Testing a gen_server with eunit

Of all the strange corners of Erlang, eunit can be one of the hardest to get your head around. Particularly if you’re used to one of the XUnit frameworks.

It is very powerful, especially once you get into generating tests; but it can also be very confusing, and sometimes the error messages are not that helpful.

I wanted to write some tests for a gen_server, using a new instance for each test, and it took me a couple of attempts to get what I wanted working. It’s very important to make sure your test can actually fail, either in advance or by mutating it, or to add some output (?debugMsg) that proves it ran.

-module(foo_server_tests).

-include_lib("eunit/include/eunit.hrl").

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> fun() -> server_is_alive(Pid) end end,
        fun(Pid) -> fun() -> something_else(Pid) end end
    ]}.

setup() ->
    ?debugMsg("setup"),
    process_flag(trap_exit, true),
    {ok, Pid} = foo_server:start_link(),
    Pid.

server_is_alive(Pid) ->
    ?assertEqual(true, is_process_alive(Pid)).

something_else(Pid) ->
    ?assertEqual(bar, gen_server:call(Pid, foo)).

cleanup(Pid) ->
    ?debugMsg("cleanup"),
    exit(Pid, kill), %% brutal kill!
    ?assertEqual(false, is_process_alive(Pid)).

We need to trap exits in the setup fun, as the process is linked to the gen_server when it starts. The tricky bit is the nested funs in the generator, if you try to call the “test” directly:

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> server_is_alive(Pid) end
    ]}.

you’ll get an error like this:

*** result from instantiator foo_server_tests:'-foo_server_test_/0-fun-2-'/1 is not a test ***

because eunit is trying to use the output of the called fun as a test. And if you return multiple tests from one block:

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun(Pid) -> [
             fun() -> server_is_alive(Pid) end,
             fun() -> something_else(Pid) end
        ] end
    ]}.

then the setup & teardown will only be called once for those tests (which may, or may not, be what you want).

UPDATE: actually this is a better approach:

-module(foo_server_tests).

-include_lib("eunit/include/eunit.hrl").

foo_server_test_() ->
    {foreach, fun setup/0, fun cleanup/1, [
        fun server_is_alive/1,
        fun something_else/1
    ]}.

server_is_alive(Pid) ->
    fun() ->
        ?assertEqual(true, is_process_alive(Pid))
    end.

something_else(Pid) ->
    fun() ->
        ?assertEqual(bar, gen_server:call(Pid, foo))
    end.

as the test names are included in the output:

$ SKIP_DEPS=true make eunit
 APP    foo.app.src
 GEN    test-dir
 GEN    eunit
======================== EUnit ========================
directory "ebin"
  module 'foo_server'
    module 'foo_server_tests'
      foo_server_tests: server_is_alive...ok
      foo_server_tests: something_else...[0.001 s] ok
      [done in 0.012 s]
    [done in 0.012 s]
  [done in 0.028 s]
=======================================================
  All 2 tests passed.

UPDATE 2: in case it wasn’t clear, this is effectively an “integration” test or black box test. The server is started, and messages are passed to it. It is also possible to “unit” test individual methods, if you don’t mind being more tightly coupled to the internal state of the server (white box). Both kinds of testing can provide value, pick whichever works for you for that scenario.

One thought on “Testing a gen_server with eunit

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s