Vim - Vim script用のモックライブラリvmockを作った

JMockのようなモックライブラリのVim script版が見当たらなかったので作りました。

kannokanno/vmock

どういうときに使うか

依存プラグインから分離してテストを書きたい場合です。
例えばwebapi-vimを使っている場合など。

どうやって使うか

基本的にはテストプラグインと一緒に使う予定です。
そのうちvimtestに組み込もうと思っていますが、まだ出来てはいません。
また、他のテストプラグインとも連携できるようにしたいなという思いのもとに一応設計しています。
(実際に連携できるかどうかはテストプラグイン側の仕組みにもよる)

現状では、vmock単体でのみ使うことができます。

例としてwebapi-vimに依存したスクリプトを取り上げます。

プロダクトコード

まず、プロダクトコードとしてこんなコードがあるとします。

function! s:read_feed(url)
  let feeds = []
  for item in webapi#feed#parseURL(a:url)

    " some processing...

    call add(feeds, item)
  endfor
  return feeds
endfunction
  1. 引数で渡されたurlを使ってwebapi#feed#parseURL() を呼び出します
  2. 何かしらの処理(some processing) を行ったあと、webapiの戻り値をリストに詰めます
  3. リストを返します

これのテストをしようとすると、実際に通信が発生してしまいます。
それは困るといった場合にvmockを使います。

テストコード

vmock単体で使うコード例がこちらです。
コード全体はvmock/exampleにあります。

function! s:test()
  " expected data
  let items = [s:make_item('Title-1', 'http://localhost/hoge'),
            \ s:make_item('Title-2', 'http://localhost/piyo'),
            \ s:make_item('Title-3', 'http://localhost/fuga')]
  let url = 'http://rss.slashdot.org/Slashdot/slashdot'

  try
    " A
    call vmock#mock('webapi#feed#parseURL').with(url).return(items).once()

    " B
    let feeds = s:read_feed(url)

    " C
    call s:assert(len(items), len(feeds))
    call s:assert(items[0], feeds[0])
    call s:assert(items[1], feeds[1])
    call s:assert(items[2], feeds[2])

    " D
    call vmock#verify()
  catch
    echoerr v:exception
  finally
    " E
    call vmock#clear()
  endtry
endfunction

call s:test()

Aでモックを作成しています。(vmock#mock)
加えて引数の期待値指定(with)、戻り値の設定(return)、回数指定(once)も行なっています。

Bで実際の処理を呼び出します。
内部で呼ばれるwebapi#feed#parseURLはモックになっているので実際に通信は発生しません。

Cで自前assertしています。

Dでモックの検証を行なっています。
モック呼び出し時の引数が異なっていたりすると例外が発生します。

Eでモックの定義を消して元の関数定義に復元しています。

実行

これを:sourceすると以下のように出力されます。

OK
OK
OK
OK

(quickrunで実行した場合はvmock内部の:function結果も表示されます)

今度は試しにモックの検証を失敗させてみます。

期待する引数と実際の引数が違った

実際の呼び出し時の引数を適当にしてみます。

    let feeds = s:read_feed('invalid')
OK
OK
OK
OK
function <SNR>448_test, line 19
VMock:The args[0] expected: 'http://rss.slashdot.org/Slashdot/slashdot'. but received: 'invalid'.

期待する呼び出し回数ではなかった

1度だけ(once)指定になっていますが、わざと2度呼んでみます。

    let feeds = s:read_feed(url)
    let feeds = s:read_feed(url)
OK
OK
OK
OK
function <SNR>448_test, line 20
VMock:expected: only once. but received: 2 times.

おわり

こんな感じで使います。詳しくはREADMEとヘルプをご覧ください。