10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2021

Day 19

Moxを使ってみる

Last updated at Posted at 2022-05-11

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

モックと対象関数が呼ばれる回数が一致したため正常に終了しました

10
5
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
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?