From 42fda99cf6ed4ec29740a93b2052a747620f9777 Mon Sep 17 00:00:00 2001 From: Keith Salisbury Date: Wed, 29 Jan 2020 11:58:46 +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 --only mocked_service ``` --- lib/example/worker.ex | 51 ++++++++++++++++++++----------------------- test/worker_test.exs | 13 ++++++----- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/example/worker.ex b/lib/example/worker.ex index aed9473..b811a88 100644 --- a/lib/example/worker.ex +++ b/lib/example/worker.ex @@ -3,15 +3,7 @@ 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 + @service Application.get_env(:example, :service, DefaultService) def start_link(init_arg \\ []) do GenServer.start_link(__MODULE__, init_arg, name: __MODULE__) @@ -21,28 +13,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}} + + 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_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} + 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/worker_test.exs b/test/worker_test.exs index b734bfd..a367e11 100644 --- a/test/worker_test.exs +++ b/test/worker_test.exs @@ -4,29 +4,30 @@ defmodule Example.WorkerTest do alias Example.Worker describe "default service" do + @tag :default_service test "returns default service foo" do assert Worker.get_foo() =~ ~s(default says foo) end end describe "mocked service" do + setup :verify_on_exit! + setup do - # Normally you would add this to `test_helper.ex`, or `support/mocks.ex + # Normally you would add this to `test_helper.ex`, or `support/mocks.ex` Mox.defmock(Example.MockService, for: Example.ServiceBehaviour) - Example.MockService - |> expect(:foo, fn -> "setup all says foo" end) - :ok end - setup :verify_on_exit! - + @tag :mocked_service test "returns mocked service foo" do Example.MockService |> expect(:foo, fn -> "mock says foo" end) |> allow(self(), Process.whereis(Worker)) + Worker.initialise_foo() + assert Worker.get_foo() =~ ~s(mock says foo) end end