Elixir: Testing without starting supervision tree
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
- Here we adding new attribute - aliases - they are simple mix tasks. you can reed more here.
- Here we define your new alias. Change
test
totest --no-start
, so when we runmix test
it opens intomix 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)
- Here we manually start
:ecto
application with all it’s dependencies. You can read more about this function here. - 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()
- We should load application first, to get access to it’s specification.
- 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!
Comments