Search code examples
testingerlangelixirerlang-otpgen-server

How to test handle_cast in a GenServer properly?


I have a GenServer, which is responsible for contacting an external resource. The result of calling external resource is not important, ever failures from time to time is acceptable, so using handle_cast seems appropriate for other parts of code. I do have an interface-like module for that external resource, and I'm using one GenServer to access the resource. So far so good.

But when I tried to write test for this gen_server, I couldn't figure out how to test the handle_cast. I have interface functions for GenServer, and I tried to test those ones, but they always return :ok, except when GenServer is not running. I could not test that.

I changed the code a bit. I abstracted the code in handle_cast into another function, and created a similar handle_call callback. Then I could test handle_call easily, but that was kind of a hack.

I would like to know how people generally test async code, like that. Was my approach correct, or acceptable? If not, what to do then?


Solution

  • A cast request is of the form:

    Module:handle_cast(Request, State) -> Result
    
    Types:
    Request = term()
    State = term()
    Result = {noreply,NewState} | 
             {noreply,NewState,Timeout} | 
             {noreply,NewState,hibernate} |
             {stop,Reason,NewState}
    NewState = term()
    Timeout = int()>=0 | infinity 
    Reason = term()
    

    so it is quite easy to perform unit test just calling it directly (no need to even start a server), providing a Request and a State, and asserting the returned Result. Of course it may also have some side effects (like writing in an ets table, modifying the process dictionary...) so you will need to initialize those resources before, and check the effect after the assert.

    For example:

    test_add() ->
        {noreply,15} = my_counter:handle_cast({add,5},10).