0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ひとりカレンダー】ClojureAdvent Calendar 2024

Day 11

Clojure テストでモックを使う

Last updated at Posted at 2024-12-10

advent_calendar_2024.png

Advent Calendar 2024 Day 11

今日はClojureでのテスト時にモックを使う方法について書こうと思います

題材として、Todoの一覧を取得する部分をmockし、取得後の処理のみ部分のテストを書いてみます

(ns todo.core
  (:require [todo.http :as http]))

(defn get-todo-titles
  []
  (let [{:keys [body]} (http/get-todos)]
    (map :title body)))

例えばこのような、todo.httpからget-todosという関数を呼び出し、タイトルだけを返却するようなget-todo-titlesがあるとします。

get-todo-titlesの単体テストを書くにあたり、todo.http/get-todosをモックしていきます

1. with-redefs

Clojureの標準ライブラリに含まれるwith-redefsを使うことで、テストのスコープ内でのみ、グローバルな関数を一時的にモックに置き換えることができます。

これを使ってtodo.http/get-todosをモックします

(t/deftest get-todo-titles-test
  (t/testing "Todoのタイトルを取得できる"
    (let [todos [{:id 1 :title "朝食を食べる" :completed false}
                 {:id 2 :title "仕事に行く" :completed false}]]
      (with-redefs [http/get-todos (fn [] {:status 200 :body todos})]
        (t/is (= (sut/get-todo-titles) ["朝食を食べる" "仕事に行く"]))))))
$ lein test

lein test todo.core-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

無事mock化できました

2. ライブラリを使う

今回は mockfn を使ってみようと思います

mockfnは、providingverifyingという関数があり、これが便利です

providing

(ns todo.core-test
  (:require [clojure.test :as t]
            [todo.core :as sut]
            [todo.http :as http]
            [mockfn.macros :as mockfn]))

(t/deftest get-todo-titles-test
  (t/testing "特定のTodoのタイトルを取得できる"
    (mockfn/providing
     [(http/get-todo 1) {:status 200 :body {:id 1 :title "朝食を食べる"}}
      (http/get-todo 2) {:status 200 :body {:id 1 :title "仕事に行く"}}]
     (t/is (= (sut/get-todo-title 1) "朝食を食べる"))
     (t/is (= (sut/get-todo-title 2) "仕事に行く")))))

ここでは、

id title completed
1 朝食を食べる false
2 仕事に行く true

という状態を想定し、http/get-todoの関数をモックしてスタブレスポンスを返しています。

$ lein test

lein test todo.core-test

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.

無事モックできたようでテストが通りました。

ただこれだとhttp/get-todoを本当に使われたのか不明ですし、verifyしたくなることがあると思います。
その時のために次のverifyingがあります

verifying

verifyingでは、モックした関数が指定した回数呼ばれたかどうかをチェックします

(ns todo.core-test
  (:require [clojure.test :as t]
            [todo.core :as sut]
            [todo.http :as http]
            [mockfn.macros :as mockfn]
            [mockfn.matchers :as matchers]))

(t/deftest get-todo-titles-test
  (t/testing "特定のTodoのタイトルを取得でき、http/get-todoが呼ばれている"
    (mockfn/verifying
     [(http/get-todo 1) {:status 200 :body {:id 1 :title "朝食を食べる" :completed false}} (matchers/exactly 1)
      (http/get-todo 2) {:status 200 :body {:id 1 :title "仕事に行く" :complated true}} (matchers/exactly 1)]
     (t/is (= (sut/get-todo-title 1) "朝食を食べる"))
     (t/is (= (sut/get-todo-title 2) "仕事に行く")))))

このようにすることで、先ほどのテストがさらに良くなり、http/get-todoが1度だけ呼ばれていることもテストすることができました

$ lein test

lein test todo.core-test

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.

試しに片方だけ2回呼ばれたことを検証し、失敗してみます

$ lein test

lein test todo.core-test

ERROR in (get-todo-titles-test) (mock.cljc:23)
Uncaught exception, not in assertion.
expected: nil
  actual: clojure.lang.ExceptionInfo: Expected todo.http$get_todo@26a2f7f9 with arguments [1] exactly 2 times, received 1.
{}
 at mockfn.mock$verify.invokeStatic (mock.cljc:23)
    mockfn.mock$verify.invoke (mock.cljc:17)
    todo.core_test$fn__781$fn__782.invoke (core_test.clj:27)
    clojure.core$with_redefs_fn.invokeStatic (core.clj:7582)
...
Ran 1 tests containing 3 assertions.
0 failures, 1 errors.
Tests failed

このように、[1] exactly 2 timesということで、モックした関数が呼ばれた関数の違いでテストが失敗するようになりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?