chrisalley

chrisalley

How to describe many contexts in ExUnit without a hierarchy

ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe blocks are forbidden, and instead developers should “build on top of named setups”. The following example used:

defmodule UserManagementTest do
  use ExUnit.Case, async: true

  describe "when user is logged in and is an admin" do
    setup [:log_user_in, :set_type_to_admin]

    test ...
  end

  describe "when user is logged in and is a manager" do
    setup [:log_user_in, :set_type_to_manager]

    test ...
  end

  defp log_user_in(context) do
    # ...
  end
end

For comparsion, here’s how I might lay out the same test using RSpec:

RSpec.describe UserManagement, type: :model do
  describe "some method" do
    context "when user is logged in" do
      before do
        ..
      end

      context "when user is an admin" do
        before do
          ...
        end

        it ...
      end

      context "when user is a manager" do
         before do
           ...
         end

         it ...
      end
    end
  end
end

The reasoning for ExUnit way is solid - it’s easier to glance at a single describe block in isolation and understand exactly what is going on. However, coming from RSpec, the ability the lay out multiple contexts as described above is useful for covering all scenarios. In particular, if there’s three or four different contexts that need to be checked, including different combinations of each, having a hierarchy can help to lay out all of the different pathways through the code. If the contexts are keep flat, we would end up with very long describe/context strings, e.g. “when user is logged in and user is an admin and user is an author and user is a publisher”.

Since ExUnit doesn’t support nested describe/context blocks, what are some alternative strategies to describe these more complex sets of contexts?

Most Liked

josevalim

josevalim

Creator of Elixir

Since ExUnit doesn’t support nested describe/context blocks, what are some alternative strategies to describe these more complex sets of contexts?

Your first approach is the alternative. Sometimes we get too hung up on reducing duplication and we forget the downsides of trying to “unify” everything, such as coupling and loss of context. ExUnit wants you to define everything in two lines (the describe and setup) because the next person reading the file will have a better understanding of what is happening.

Go back to a project that uses nested describes/context and try to find out what a single test is doing. This is going to be the process:

  1. You will find the test and try to find the outer most describe
  2. Put “user is logged in” (the outermost describe) in your brain stack
  3. Then find and put “and is a manager” in your brain stack
  4. Then find and put “with multiple accounts” in your brain stack
  5. Remove “with multiple accounts” from your brain stack (because that context has ended without reaching your test)
  6. Then find and put “with a single account” in your brain stack
  7. Found the test

It is such a hassle.

The describe is there to describe what your test is doing. Be descriptive.

43
Post #3
chrisalley

chrisalley

Thanks for the detailed response, @josevalim. Having dealt with some complex nested specs, I can certainly relate to the desire for decoupling and explicitness when examining individual tests.

My main concern is how to structure contexts without a literal tree of code. But as @georgeguimaraes suggested, the contexts could be placed into seperate files. This would in practice create a kind of hierarchy, with the folder containing the context files acting as the parent. If required, further subfolders could be created to represent grandparent contexts, creating a tree of folders and files instead of a tree of code. Each test would still have the full description of the context in the describe statement so that the test can be understood in isolation, while the folder structure would represent the structure of the many pathways through the code. I imagine a lot of RSpec tests could benefit from this kind of restructuring too, rather than being placed in one giant file that covers every context that the class being tested can be in.

georgeguimaraes

georgeguimaraes

What do you think about breaking each context (logged in) in its own file?

tjstankus

tjstankus

Having many pathways through your code is complicating your tests. If you’re going for full test coverage, your tests end up as the Cartesian product of the branches in your code, requiring new test cases exponentially as you add branches. The underlying solution is not to hone in on a way to nest your tests, but to able to test contexts independently. In Ruby and Elixir this is achieved through polymorphism. To use your example, you would test admin behavior and manager behavior independently and treat user polymorphically in code that depends upon user.

Where Next?

Popular in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New

Other popular topics Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New

We're in Beta

About us Mission Statement