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