From 2a44fea7d3d6e0078d003b6a269c67ee27d2d6b6 Mon Sep 17 00:00:00 2001 From: Keith Salisbury Date: Wed, 29 Jan 2020 13:17:45 +0000 Subject: [PATCH] Conditionally initalise Worker state This commit updates the `Worker` `GenServer` so that the `handle_continue/2` callback is only invoked when the service is set to `DefaultService`. This module already knows about `DefaultService` so this seems slightly better than pattern matching on `MockService`, however if there are alternative "production" services, other than `DefaultService` the `Worker.init/1` would need to change. Because we are unable to call `handle_continue/2` directly from the `worker_test.ex` we need to add `initialise_foo/0` which finds the pid and sends the `:initialise_foo` message. This same message is also used in the `handle_continue/2` callback. The tests can be run using: ``` mix test ``` --- lib/example/worker.ex | 51 +++++++++++++++++++++---------------------- test/test_helper.exs | 2 ++ test/worker_test.exs | 31 +++++++++++++------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/lib/example/worker.ex b/lib/example/worker.ex index aed9473..83e9885 100644 --- a/lib/example/worker.ex +++ b/lib/example/worker.ex @@ -3,12 +3,6 @@ defmodule Example.Worker do alias Example.DefaultService - # When using ElixirLS, defining the service at compile time will result in an - # error because ElixirLS always compiles using MIX_ENV=test which mean @service - # will always be set to MockService, which does not have `foo/0` - # @service Application.get_env(:example, :service, DefaultService) - # @service DefaultService - def service() do Application.get_env(:example, :service, DefaultService) end @@ -21,28 +15,33 @@ defmodule Example.Worker do GenServer.call(__MODULE__, :get_foo) end + def initialise_foo() do + pid = Process.whereis(__MODULE__) + send(pid, :initialise_foo) + end + def init(_init_arg) do initial_state = "no foo for you" - {:ok, initial_state, {:continue, :get_foo_from_service}} - end - - def handle_continue(:get_foo_from_service, _state) do - # And here lies the problem. We want to call our service to get - # whatever inital state it provides, but in doing so, we break - # in the test environment because the MockService doesn't have - # a function called `foo/0` until it can be defined in the expects - # block within the test - by that time, this code has already - # been executed because this GenServer is part of the staticly - # defined supervision tree in `application.ex`. - - value_of_foo = - if function_exported?(service(), :foo, 0) do - service().foo() - else - "#{inspect(service())} does not support foo" - end - - {:noreply, value_of_foo} + + case service() do + DefaultService -> + {:ok, initial_state, {:continue, :get_foo_from_service}} + + _ -> + {:ok, initial_state} + end + end + + def handle_continue(:get_foo_from_service, state) do + send(self(), :initialise_foo) + + {:noreply, state} + end + + def handle_info(:initialise_foo, _state) do + IO.inspect("initialising foo now") + new_state = service().foo() + {:noreply, new_state} end def handle_call(:get_foo, _from, state) do diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..ef150f8 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,3 @@ +Mox.defmock(Example.MockService, for: Example.ServiceBehaviour) + ExUnit.start() diff --git a/test/worker_test.exs b/test/worker_test.exs index b734bfd..675acf8 100644 --- a/test/worker_test.exs +++ b/test/worker_test.exs @@ -3,31 +3,30 @@ defmodule Example.WorkerTest do import Mox alias Example.Worker - describe "default service" do - test "returns default service foo" do - assert Worker.get_foo() =~ ~s(default says foo) - end + setup do + Worker.initialise_foo() + + :ok end - describe "mocked service" do - setup do - # Normally you would add this to `test_helper.ex`, or `support/mocks.ex - Mox.defmock(Example.MockService, for: Example.ServiceBehaviour) + setup :verify_on_exit! + describe "mocked service" do + @tag :mocked_service + test "returns mocked service foo" do Example.MockService - |> expect(:foo, fn -> "setup all says foo" end) + |> expect(:foo, fn -> "mock says foo" end) + |> allow(self(), Worker) - :ok + assert Worker.get_foo() =~ ~s(mock says foo) end - setup :verify_on_exit! - - test "returns mocked service foo" do + test "returns mocked service moo" do Example.MockService - |> expect(:foo, fn -> "mock says foo" end) - |> allow(self(), Process.whereis(Worker)) + |> expect(:foo, fn -> "mock says moo" end) + |> allow(self(), Worker) - assert Worker.get_foo() =~ ~s(mock says foo) + assert Worker.get_foo() =~ ~s(mock says moo) end end end