ExUnit Tags vs. Generic Functions

I’m trying to get some advice on when I should use ExUnit tags vs. generic functions.

For example in my Phoenix controller tests I need to create users before some of my tests run, so I use tags like @tag :test_user and then in the setup block I do something like if config[:test_user] do ..., then I match on %{user: user} in the test. I believe this is the way that I learned from Programming Phoenix 1.2.

However, in the new generators I see that they have functions like create_user(_) ... in the tests which would serve basically the same function as the configuration which I stated above using tags and then in each describe block calling setup [:create_entry] or calling create_entry(_) directly in the test.

To me, tags in other testing frameworks for other languages represent things which describe groups of tests, and can be used for filtering tests, but that role is kind of filled by the describe attribute for ExUnit.

My question is, what do you use and why? I’m trying to see what other people think about best practices on this. Thanks all :slight_smile:

1 Like

I generally use tags for things like :slow or :focus which can then be included or excluded from the command line.

For setup, I prefer the named setup functions, along with the use of describe blocks to group related tests.

That’s what it seems like the new 1.3 generators are pushing towards. However, the functions that I would need to call don’t necessarily always correspond to the describe blocks that I have. For example, I may have a describe "index" ... block fory users controller but not all of the tests in that block require setup :create_user to be called. As far as I know you can’t call setup blocks for specific tests, unless that can be done?

Generic functions you can just as easily call directly from within the test.

1 Like

Yeah that’s what I’m planning to do however it seems a bit messy to have some of the functions called by setup :create_user outside the tests and others called by create_user() inside the actual test.

I’ve just gone through my tests and noticed that functions that are called through setup usually do several things/combine several fixtures (create user + create a token for user + create a membership in a chat room) and are not called inside the tests themselves.

And whenever I need to create just a user inside some specific test, I call fixtures like fixture(:user) (with optional attrs as the second argument).

For example, in a socket test I need to test whether users can connect to sockets, so I do something like

  describe "connect" do
    test "with valid token" do
      %User.AuthToken{user: %User{} = user, key: token} = fixture(:auth_token)

      {:ok, %Phoenix.Socket{} = socket} = connect(Web.UserSocket, %{"token" => token})

      assert socket.assigns.user_id == user.id
      assert socket.id == "user_socket:#{user.id}"
    end

    test "with invalid token" do
      assert :error == connect(Web.UserSocket, %{"token" => "invalid"})
    end

    test "without a token" do
      assert :error == connect(Web.UserSocket, %{})
    end
  end

but in channels, where the user is supposed to be already connected, I can do

  describe "some test group" do
    setup [:create_resources, :connect_to_socket]

    test "some test", %{socket: socket, resources: resources} do
      # ...
    end
  end

  defp connect_to_socket(_context) do
    %User.AuthToken{user: %User{} = user, key: token} = fixture(:auth_token)
    {:ok, %Phoenix.Socket{} = socket} = connect(Web.UserSocket, %{"token" => token})
    {:ok, _reply, socket} = subscribe_and_join(socket, "...", %{})
    {:ok, %{user: user, socket: socket}}
  end

  defp create_resources(_context) do
    # ...
  end
3 Likes

Yeah that’s what I’m trying to do however then that would require me to group tests in describe blocks by their required setup instead of what they are testing. For example in my blog app only logged in users should be able to view unpublished posts. Therefore, I have a describe "index" ... block with multiple tests in it but not all of those tests need a setup block logging a user in first. Some tests need a logged in user to check if they can see unpublished posts and some don’t to ensure that a non logged in user can’t see those posts.

I don’t know how to model this with ExUnit without tags which seem very messy :confused: