Skip to content

Instantly share code, notes, and snippets.

@kannokanno
Created July 22, 2012 08:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kannokanno/3158930 to your computer and use it in GitHub Desktop.
Save kannokanno/3158930 to your computer and use it in GitHub Desktop.
Mock sample for Vim script
function! s:mock(context)
let mock = {'context' : a:context, 'instance_map' : {}}
function! mock.function(name)
let instance = s:mock_instance(self.context, a:name)
let self.instance_map[instance.funcname_full] = instance
return instance
endfunction
function! mock.assert()
try
for v in values(self.instance_map)
call v.assert()
endfor
catch
call s:error(v:errmsg)
finally
unlet g:mock
endtry
endfunction
function! mock.assert_args(key, ...)
if has_key(self.instance_map, a:key)
call self.instance_map[a:key].assert_args(a:1)
endif
endfunction
function! mock.called(key)
if has_key(self.instance_map, a:key)
call self.instance_map[a:key].called()
endif
endfunction
" @see s:rewrite_func()
" フックする関数にて参照するためグローバルに定義する
let g:mock = deepcopy(mock)
return g:mock
endfunction
function! s:mock_instance(context, funcname)
let mock = {
\ 'context' : a:context,
\ 'funcname' : a:funcname,
\ 'funcname_full' : s:get_funcname(s:get_func(a:context, a:funcname)),
\ '_expect_args' : [],
\ '_called_actual' : 0,
\ '_called_expected' : 0,
\}
let mock['_original_arg_define'] = s:record_original_arg_define(s:get_funcbody(mock.funcname_full))
let mock['_original_body_define'] = s:record_original_body_define(s:get_funcbody(mock.funcname_full))
let mock['_rewrite_value'] = "\n" . mock._original_body_define
function! mock.once()
let self._called_expected = 1
call s:rewrite_func(self)
return self
endfunction
function! mock.any()
let self._called_expected = -1
call s:rewrite_func(self)
return self
endfunction
function! mock.with(...)
let self._expect_args = len(a:000) == 0 ? [] : a:000
return self
endfunction
function! mock.return(value) abort
let self._rewrite_value = (type(a:value) == type('') && !empty(a:value)) ? "\nreturn '" . a:value . "'" : a:value
return self
endfunction
function! mock.throw(exception)
let self._rewrite_value = printf("\nthrow '%s'", a:exception)
return self
endfunction
function! mock.callback(func) abort
let callback = printf("\nreturn call('%s', a:000)", s:get_funcname(s:get_func(self.context, a:func)))
let self._rewrite_value = callback
return self
endfunction
function! mock.assert()
try
if self._called_expected != -1
if self._called_actual != self._called_expected
call s:error(printf('Function %s called expected %s but was %s', self.funcname, self._called_expected, self._called_actual))
endif
endif
finally
exec printf("function! %s(%s)\n%s\nendfunction", self.funcname_full, self._original_arg_define, self._original_body_define)
endtry
endfunction
function! mock.assert_args(...)
for i in range(len(self._expect_args))
let expected = self._expect_args[i]
let actual = a:1[i]
try
if actual != expected
call s:error(printf('Function %s args expected %s but was %s', self.funcname, string(self._expect_args), string(a:1)))
endif
finally
unlet expected
unlet actual
endtry
endfor
endfunction
function! mock.called()
let self._called_actual += 1
return self
endfunction
return mock
endfunction
" @see http://mattn.kaoriya.net/software/vim/20090826003359.htm
function! s:get_sid(context)
let out = ''
redir => out
silent! scriptnames
redir END
let sid_map = {}
let pattern = '^\s*\(\d\+\):\s*\(.*\)$'
for line in split(out, "\n")
let sid_map[tolower(expand(substitute(line, pattern, '\2', '')))] = substitute(line, pattern, '\1', '')
endfor
return sid_map[tolower(a:context)]
endfunction
function! s:get_func(context, funcname)
return function(printf('<SNR>%s_%s', s:get_sid(a:context), a:funcname))
endfunction
function! s:get_funcname(func)
return type(a:func) == type(function('tr'))
\ ? substitute(string(a:func), "^function('\\(.*\\)')$", '\1', '')
\ : a:func
endfunction
function! s:get_funcbody(funcname)
let out = ''
redir => out
silent! exec 'function '.a:funcname
redir END
return split(out, '\n')
endfunction
function! s:build_func_define(name, args, body)
return printf("function! %s(%s)\n%s\nendfunction", a:name, a:args, a:body)
endfunction
function! s:rewrite_func(mock)
let key = a:mock.funcname_full
let args = map(split(a:mock._original_arg_define, ',\s*'), "'a:'.v:val")
let body = printf("call g:mock.called('%s')\ncall g:mock.assert_args('%s', [%s])%s", key, key, join(args, ','), a:mock._rewrite_value)
let define = s:build_func_define(s:get_funcname(s:get_func(a:mock.context, a:mock.funcname)), a:mock._original_arg_define, body)
execute define
endfunction
function! s:record_original_arg_define(lines)
return substitute(a:lines[0], ".*function.*(\\(.*\\))", '\1', '')
endfunction
function! s:record_original_body_define(lines)
return join(map(a:lines[1:len(a:lines) - 2], 'substitute(v:val, "^[0-9]*", "", "")'), "\n")
endfunction
function! s:error(msg)
echohl Error
echo a:msg
echohl NONE
endfunction
" ------------------
" use sample
" sample function
function! s:echo()
echo 100
endfunction
function! s:hello(text1, text2)
return 'Hello, ' . a:text1 . ' and ' . a:text2
endfunction
" -ケース1:引数や戻り値は特にいじらず、一度だけ呼ばれることを検証する
echo 'case:1-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('echo').once()
call s:echo()
call s:mock.assert()
unlet s:mock
echo '-------------------'
" -ケース2:実行された際の処理をフックし、一度だけ呼ばれることを検証する
function! s:echo_callback()
echo 'callback dayo'
endfunction
echo 'case:2-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('echo').callback('echo_callback').once()
call s:echo()
" => callback dayo
call s:mock.assert()
call s:echo()
" => 100
unlet s:mock
echo '-------------------'
" -ケース3:例外を発生させるようにし、一度だけ呼ばれることを検証する
echo 'case:3-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('echo').throw('booo').once()
try
call s:echo()
catch /booo/
echo 'error dayo'
endtry
call s:mock.assert()
call s:echo()
" => 100
unlet s:mock
echo '-------------------'
" -ケース4:引数の検証をし、一度だけ呼ばれることを検証する
echo 'case:4-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('hello').with('World', 'Vim').once()
echo s:hello('World', 'Vim')
"echo s:hello('World', 'Emacs')
" => Function hello args expected ['World', 'Vim'] but was ['World', 'Emacs']
call s:mock.assert()
unlet s:mock
echo '-------------------'
" -ケース5:戻り値をフックし、一度だけ呼ばれることを検証する
echo 'case:5-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('hello').return('hogehoge').once()
echo s:hello('World', 'Vim')
" => hogehoge
call s:mock.assert()
echo s:hello('World', 'Vim')
" => Hello, World and Vim
unlet s:mock
echo '-------------------'
" -ケース6:引数の検証と戻り値をフックし、一度だけ呼ばれることを検証する
echo 'case:6-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('hello').with('World', 'Vim').return('hogehoge').once()
echo s:hello('World', 'Vim')
" => hogehoge
call s:mock.assert()
echo s:hello('World', 'Vim')
" => Hello, World and Vim
unlet s:mock
echo '-------------------'
" -ケース7:引数の検証と戻り値をフックする。呼び出し回数は検証しない
echo 'case:7-------------'
let s:mock = s:mock(expand('%:p'))
call s:mock.function('hello').with('World', 'Vim').return('hogehoge').any()
echo s:hello('World', 'Vim')
" => hogehoge
echo s:hello('World', 'Vim')
" => hogehoge
echo s:hello('World', 'Vim')
" => hogehoge
call s:mock.assert()
echo s:hello('World', 'Vim')
" => Hello, World and Vim
unlet s:mock
echo '-------------------'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment