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
```
  • Loading branch information
ktec committed Jan 29, 2020
1 parent 578fddd commit 2a44fea
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 42 deletions.
51 changes: 25 additions & 26 deletions lib/example/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
Mox.defmock(Example.MockService, for: Example.ServiceBehaviour)

ExUnit.start()
31 changes: 15 additions & 16 deletions test/worker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2a44fea

Please sign in to comment.