Brightの対象
Webアプリ開発 Elixir > クラス1 > Elixirテスト/デバッグ > ユニットテスト > mox
動作環境
$ cat /etc/os-release | grep VER
VERSION="20.04.4 LTS (Focal Fossa)"
VERSION_ID="20.04"
VERSION_CODENAME=focal
$ elixir -v
Erlang/OTP 24 [erts-12.3.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit]
Elixir 1.13.0 (compiled with Erlang/OTP 24)
プロジェクトの作成
$ mix new ymn
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/ymn.ex
* creating test
* creating test/test_helper.exs
* creating test/ymn_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ymn
mix test
Run "mix help" for more commands.
動作確認
$ cd ymn
$ mix test
Compiling 1 file (.ex)
Generated ymn app
..
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 doctest, 1 test, 0 failures
Randomized with seed 33659
ビヘイビアを定義する
lib/ymn_behaviour.ex
defmodule YmnBehaviour do
@callback say(msg :: String.t()) :: :ok
end
lib/ymn.ex
を修正
defmodule Ymn do
+ @behaviour YmnBehaviour
@moduledoc """
Documentation for `Ymn`.
"""
@doc """
Hello world.
## Examples
iex> Ymn.hello()
:world
"""
def hello do
:world
end
end
試しにsayを未定義のままtestを実行
sayの関数の警告が出ることを確認
mix test
Compiling 1 file (.ex)
warning: function say/1 required by behaviour YmnBehaviour is not implemented (in module Ymn)
lib/ymn.ex:1: Ymn (module)
..
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 doctest, 1 test, 0 failures
sayを実装
lib/ymn.ex
を修正
defmodule Ymn do
@behaviour YmnBehaviour
@moduledoc """
Documentation for `Ymn`.
"""
@doc """
Hello world.
## Examples
iex> Ymn.hello()
:world
"""
def hello do
:world
end
+ def say(msg) do
+ IO.puts("Hello!")
+ IO.puts(msg)
+ :ok
+ end
end
動作確認
iex -S mix
Erlang/OTP 24 [erts-12.3.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit]
Interactive Elixir (1.13.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Ymn.say("hoge")
Hello!
hoge
:ok
mixにMoxの定義する
mix.exs
を修正する
defmodule Ymn.MixProject do
use Mix.Project
def project do
[
app: :ymn,
version: "0.1.0",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
+ {:mox, "~> 1.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
mix deps.getをしておく
$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
mox 1.0.1
* Getting mox (Hex package)
テスト環境と実行環境で切り替える仕組みを作る
lib/ymn_behaviour.ex
を修正する
defmodule YmnBehaviour do
@callback say(msg :: String.t()) :: :ok
+ def get_ymn_module do
+ Application.get_env(:ymn, :ymn_module, Ymn)
+ end
end
テスト環境で動作するように修正
この段階ではまだMoxを使わずに実装してみる
※Doctestも実験の為廃止してます
test/ymn_test.exs
を修正
defmodule YmnTest do
use ExUnit.Case
- doctest Ymn
- test "greets the world" do
- assert Ymn.hello() == :world
- end
+ test "say" do
+ ymn_module = YmnBehaviour.get_ymn_module()
+ assert ymn_module.say("hoge") == :ok
+ end
end
現時点で動作確認をしてみる
$ mix test
Hello!
hoge
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures
ここから本題Moxを定義してみる
test/test_helper.exs
を修正
ExUnit.start()
+ Mox.defmock(YmnMock, for: YmnBehaviour)
+ Application.put_env(:ymn, :ymn_module, YmnMock)
test/ymn_test.exs
を修正
mockと出力すモックを定義します
defmodule YmnTest do
use ExUnit.Case
+ import Mox
+ setup :verify_on_exit!
+ test "say" do
+ YmnMock
+ |> expect(:say, fn _ ->
+ IO.puts("mock")
+ :ok
+ end)
get_ymn_module = YmnBehaviour.get_ymn_module()
assert get_ymn_module.say("hoge") == :ok
end
end
mix test実行してみます
Moxの内容に置き換えることができました
mix test
mock
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures
通常の実行環境は、lib/ymn.ex
の内容が実行されることが確認できました
iex -S mix
Erlang/OTP 24 [erts-12.3.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit]
==> mox
Compiling 3 files (.ex)
Generated mox app
==> ymn
Compiling 2 files (.ex)
Generated ymn app
Interactive Elixir (1.13.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ymn_module = YmnBehaviour.get_ymn_module()
Ymn
iex(2)> ymn_module.say("hoge")
Hello!
hoge
:ok
verify_on_exit!の実験
verify_on_exit!を追加時にモック(expect)と対象の関数呼ばれた回数が一致するか検証する
test/ymn_test.exs
を修正
expectを追加
defmodule YmnTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
test "say" do
YmnMock
|> expect(:say, fn _ ->
IO.puts("mock")
:ok
end)
+ |> expect(:say, fn _ ->
+ IO.puts("mock2")
+ :ok
+ end)
get_ymn_module = YmnBehaviour.get_ymn_module()
assert get_ymn_module.say("hoge") == :ok
end
end
$ mix test
mock
1) test say (YmnTest)
test/ymn_test.exs:7
** (Mox.VerificationError) error while verifying mocks for #PID<0.219.0>:
* expected YmnMock.say/1 to be invoked 2 times but it was invoked once
stacktrace:
(mox 1.0.1) lib/mox.ex:713: Mox.verify_mock_or_all!/3
(ex_unit 1.13.0) lib/ex_unit/on_exit_handler.ex:143: ExUnit.OnExitHandler.exec_callback/1
(ex_unit 1.13.0) lib/ex_unit/on_exit_handler.ex:129: ExUnit.OnExitHandler.on_exit_runner_loop/0
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 test, 1 failure
モックと対象関数が呼ばれる回数が不一致の為エラーになりました
モックと対象関数が呼ばれる回数を合わせてみる
test/ymn_test.exs
を修正
get_ymn_module.sayを追加
defmodule YmnTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
test "say" do
YmnMock
|> expect(:say, fn _ ->
IO.puts("mock")
:ok
end)
|> expect(:say, fn _ ->
IO.puts("mock2")
:ok
end)
get_ymn_module = YmnBehaviour.get_ymn_module()
assert get_ymn_module.say("hoge") == :ok
+ assert get_ymn_module.say("hogehoge") == :ok
end
end
$ mix test
mock
mock2
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures
Randomized with seed 587642
モックと対象関数が呼ばれる回数が一致したため正常に終了しました