Skip to content

Commit

Permalink
Conditionally initalise Worker state
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
ktec committed Jan 29, 2020
1 parent 3d78103 commit 145af2e
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 32 deletions.
51 changes: 24 additions & 27 deletions lib/example/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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
Expand Down
14 changes: 9 additions & 5 deletions test/worker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,33 @@ 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)
# 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
Expand Down

0 comments on commit 145af2e

Please sign in to comment.