5. Testing `handle_info 2` - abarr/remote GitHub Wiki
First, I refactored my start_link
function to simplify the naming of a server.
"/lib/remote/users/user_server.ex"
...
def start_link(opts) do
name = Access.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, nil, name: name)
end
...
Because I am testing a message being sent on a timer, I need to pass the @update_interval
to the GenServer
when I create it, so I need to extend the start_link
and init
functions. I will also need to hold the value in the state so the scheduling function can use it.
I updated the GenServer
so that @max
and @min
are configurable and changed the start_link
and init
functions to store state and configuration.
"/lib/remote/users/user_server.ex"
...
@max Application.get_env(:remote, :update_interval) || 100
@min Application.get_env(:remote, :update_interval) || 0
def start_link(opts) do
name = Access.get(opts, :name, __MODULE__)
update_interval = Access.get(opts, :update_interval, @update_interval)
GenServer.start_link(__MODULE__, %{update_interval: update_interval}, name: name)
end
@impl true
def init(config) do
schedule_update(config.update_interval)
{:ok, {%{max_number: Enum.random(0..100), timestamp: nil}, config}}
end
...
I also changed the schedule_update
calls to use the config.update_interval
rather than the module
attribute
...
schedule_update(config.update_interval)
...
A quick test and everything is working as expected.
$ iex -S mix
Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]
Compiling 12 files (.ex)
Generated remote app
Interactive Elixir (1.13.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> [debug] QUERY OK source="users" db=954.5ms decode=2.7ms queue=1.1ms idle=67.7ms
SELECT count(u0."id") FROM "users" AS u0 []
Number of rows updated: 1000000
[debug] QUERY OK db=5878.4ms queue=0.8ms idle=1039.9ms
UPDATE users
SET
points = floor(random() * (10000 - 10000)) + 10000,
updated_at = now() at time zone 'utc';
[]
[debug] QUERY OK source="users" db=143.7ms queue=0.1ms idle=1920.9ms
SELECT count(u0."id") FROM "users" AS u0 []
Number of rows updated: 1000000
[debug] QUERY OK db=4136.4ms queue=1.0ms idle=1064.6ms
UPDATE users
SET
points = floor(random() * (10000 - 10000)) + 10000,
updated_at = now() at time zone 'utc';
[]
Now I want to add a test that confirms that the :max_number
in the state has changed, as this indicates that the timer has been set and the message received by the server.
I added use Remote.DataCase, async: true
to the test file and explicitly changed the setup to enforce shared ownership.
"/test/support/data_case/ex"
...
setup _tags do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Remote.Repo, shared: true)
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
:ok
end
...
Because the tests use a sandbox, there is no data in the Users
table. So I added a test with a update_interval
of 1_000
. I then sleep for 1_500
and test that :max_number
has changed,
"test/remote/user_server_test.exs"
defmodule Remote.UserServerTest do
use ExUnit.Case, async: true
use Remote.DataCase, async: true
describe "user server test" do
@update_interval 10_000
setup do
{:ok, user_server} = start_supervised({Remote.Users.UserServer, name: __MODULE__, update_interval: @update_interval})
%{user_server: user_server}
end
test "get state", %{user_server: user_server} do
assert {%{max_number: _max_number, timestamp: nil}, %{update_interval: 10000}} = GenServer.call(user_server, :get_state)
end
test "checks server state changes based on timer" do
{:ok, server} =
Remote.Users.UserServer.start_link(name: :timer_test, update_interval: 1_000)
{%{max_number: max_number}, _} = GenServer.call(server, :get_state)
:timer.sleep(1_500)
{%{max_number: new_max_number}, _} =GenServer.call(server, :get_state)
assert max_number != new_max_number
end
end
end
Running the tests and I can confirm the GenServer
is created with the desired state and that the timer is running and the state is updating.
$ mix test
...Number of rows updated: 0
.
Finished in 2.0 seconds (2.0s async, 0.00s sync)
4 tests, 0 failures