Elixir: Testing without starting supervision tree

  • Dmitry Rubinstein
  • 26 October 2016
  • Comments

Imagine simple situation: you need to test some of your code modules, but you application is made in the best qualities and traditions of OTP framework. When your tests start, your application starts also, and supervision tree starts also, and everything is running in the same environment with your tests!

Thus, you get a bench of problems:

  • Your dev and test environments mix together;
  • You can’t test processes, that are registered with names, because they are already started by your supervision tree;
  • You can’t perform unit testing, because you are not really sure, that this testing would be really unit.

The solution is simple: Divide and conquer!


At the beginning - let’s turn of this annoying supervision tree!

Configure your mix file

The first thing is simple: dont start your application in test. Fortunately, mix test has special parameter: --no-start, which prevent any applications from starting in your test environment. Of course, we, developers, are so lazy - we don’t want to put this parameter every time when we are running tests. For more, imagine, that your team has a new member, who download the source, types mix test, and… get overkilled with tonnes of error logs. Not the funnies start on a new place, eh?

So, let’s modify aliases a bit. Change your mix.exs file in such a way:


.mix.exs

def project do
  [
    app: :my_app,
    version: "0.1.0",
    elixir: "~> 1.3",
    build_embedded: Mix.env == :prod,
    start_permanent: Mix.env == :prod,
    description: description(),
    package: package(),
    aliases: aliases(), #(1)
    deps: deps()
  ]
end
...
defp aliases do
  [
    test: "test --no-start" #(2)
  ]
end
  1. Here we adding new attribute - aliases - they are simple mix tasks. you can reed more here.
  2. Here we define your new alias. Change test to test --no-start, so when we run mix test it opens into mix test --no-start.

Configuring your ExUnit

Ok, now all applications are not started. But the question is:

Is that what you need?

Not only your application wasn’t started. You environment in working without :logger, :kernel, :ecto, and dozen applications, which are automatically started before your application. So… what to do?

The answer is: start them! The best place to do this - is before calling ExUnit.start in you test_helper.exs file. We can do this manually, if we want total control:


./test/test_helper.exs

Application.ensure_all_started(:ecto) #(1)

ExUnit.start() #(2)
  1. Here we manually start :ecto application with all it’s dependencies. You can read more about this function here.
  2. This line is already in your autogenerated test_helper.exs file.

But this is the solution, how to start all dependencies of your application without starting your application:


./test/test_helper.exs

Application.load(:my_app) #(1)

for app <- Application.spec(:my_app,:applications) do #(2)
  Application.ensure_all_started(app)
end

ExUnit.start()
  1. We should load application first, to get access to it’s specification.
  2. List all the applications, which are dependencies of yours. Then just ensure, that they are all started. You can read more about spec function here.

Of course, if you will modify your dependencies, second solution will track this changes, in comparison with first, which forces you to control everything by yourself. In all cases, now you see, that you can write your user code to control what to start in your test cases.

Updates

Thanks amberbit for good comment about Application.load function!

Dmitry Rubinstein

Elixir Developer, Architect and Evangelist. Has more then 5 years in Elixir production development, and half a dozen Hex packages under maintaining