Question

When writing code in Java, it is very helpful to embrace composition and dependency injection to make it possible and easy to do pure unit testing by mocking collaborating objects.

I find that doing the same in Erlang is less straightforward and makes for dirtier code.

That's likely to be my fault, as I'm quite new to Erlang and quite addicted to JUnit, EasyMock and java interfaces...

Let's say I have this stupid function:

%% module mymod
handle_announce(Announce) ->
    AnnounceDetails = details_db:fetch_details(Announce),
    AnnounceStats = stats_db:fetch_stats(Announce),
    {AnnounceDetails, AnnounceStats}.

When unit testing mymod, I only want to prove that details_db and stats_db are invoked with the right parameters, and that the return values are used correctly. The ability of details_db and stats_db to generate correct value is tested in other places.

To solve the problem I could refactor my code this way:

%% module mymod
handle_announce(Announce, [DetailsDb, StatsDb]) ->
    AnnounceDetails = DetailsDb:fetch_details(Announce),
    AnnounceStats = StatsDb:fetch_stats(Announce),
    {AnnounceDetails, AnnounceStats}.

And test it this way (basically stubbing the calls directly into the test module):

%% module mymod_test
handle_announce_test() ->
    R = mymod:handle_announce({announce, a_value}, [?MODULE, ?MODULE, ?MODULE]),
    ?assertEqual({details,stats}, R).

fetch_details({announce, a_value}) ->
    details.

fetch_stats({announce, a_value}) ->
    stats.

It works, but the application code becomes dirty and I always have to carry around that ugly list of modules.

I've tried a couple of mock libraries (erlymock and (this other one) but I wasn't satisfied.

How do you unit test your erlang code?

Thanks!

Was it helpful?

Solution

There are two things to consider here...

You need to separate out all your code into 2 different types of modules:

  • pure functional modules (aka side-effect free modules)
  • modules with side-effects

(You should read up on that and be sure that you understand the difference - the most typical side-effect - and the one that is in your sample code - is writing to the database).

The modules that are pure functional become trivial to test. Each exported function (by definition) always returns the same values when the same values are put in. You can use the EUnit/Assert framework that Richard Carlsson and Mickael Remond wrote. Bish-bash-bosh, job's a good 'un...

The key thing is that about 90% of your code should be in pure functional modules - you dramatically shrink your problem. (You might think this is not 'solving' your problem, merely 'reducing' it - and you would be mostly right...)

Once you have achieved this separation the best way to unit test the modules with side-effects is to use the standard test framework.

The way we do this is not to use mock-objects - but to load the database in the init_per_suite or init_per_test functions and then run the modules themselves...

The best way is to move straight over to system tests as soon as possible for this though as the unit tests are a pain to maintain - so enough unit tests to get you to a system-test round-trip and no more (even better delete the db unit tests as soon as possible).

OTHER TIPS

I second what Guthrie says. You'll be surprised by how much of your logic can be pulled out into pure functions.

One of the things I've been tying lately with the new parameterized modules is to use paramterized modules for the dependency injection. It avoids the problem with parameter lists and process dictionaries. If you can use the recent versions of erlang that might be a good fit as well.

Gordon is correct in that the main target is to test the small side-effect free functions.

But... well, it is possible to test integration as well, so lets show how one can do that.

Injection

Avoid lists to carry the parameterized dependencies. Use a record, the process dictionary, parameterized modules. The code will be less ugly.

Seams

Don't focus on variable modules as dependency seams, let processes be the seams. Hard-coding a registered process name is a lost chance at injecting a dependency.

I am just answering the question being asked directly and not trying to judge whether the author should be doing this at all.

Using meck you can write a unit test for you example as follows:

handle_announce_test() ->
    %% Given
    meck:new([details_db, stats_db]),
    meck:expect(details_db, fetch_details, ["Announce"], "AnnounceDetails"),
    meck:expect(stats_db, fetch_stats, ["Announce"], "AnnounceStats"),
    %% When
    Result = handle_announce("Announce"),
    %% Then
    ?assertMatch({"AnnounceDetails", "AnnounceStats"}, Result),
    %% Cleanup
    meck:unload().

I use strings just to emphasis that they are not something that is passed in really but rather a fake value. Thanks to syntax highlight they are easy to spot in the test code.

To be honest I am a former Java developer deeply in love with Mockito recently switched to Erlang, and now contributing to the aforementioned project.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top