Search code examples
concurrencyerlangelixirrace-conditiongen-server

Elixir Application.set_env and concurrency race condition


After reading http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ article I have in my code:

  defp rest_adapter, do: Application.get_env(:app_name, :rest_adapter)

I'm using it to "mock" rest adapter during tests and return different results and error codes.

However, during these tests, there's a race condition because I set different rest_adapter for different test cases.

Sometimes they work as intended but sometimes they don't "catch" different rest_adapter set particularly for this test.

How can avoid that problem there?


Solution

  • The issue comes from the fact that application environment is global - you can't concurrently change it in multiple places to have different values. The simplest solution is to disable concurrent tests by removing async: true this however, makes your tests slower, since they can't run concurrently.

    Fortunately there are other possible solutions. The simplest one (and arguably the most elegant one) is to pass the adapter as an option to the function that uses it, and when none is provided use the one from application environment. Another solution, when the calls happen in the same process (which is often the case) is to use process dictionary to pass the adapter, instead of relying on the application environment.

    Furthermore, it's possible to adapt hybrid solutions for example have two adapters: a real one and one returning the response from the process dictionary. Then use the process dictionary one always in the tests and just put the expected response before calling the function.

    Finally there are things like Bypass that work on a slightly different layer giving you a mocked HTTP endpoint, instead of replacing the code for calling the endpoint (which is the usual approach to "replace" http calls in tests).