Bitwise
Bitwise

Reputation: 8461

Adding a setup to tests for elixir

It looks like Exunit provides a callback for setup blocks https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html. I'm curious how it can be done with wallaby feature test. I basically walk through creating a user in every scenario when testing integration tests. I basically want to pull out the boilerplate code and use it for every test I write. Here is my current setup for integration test.

    test "a user can create a location", %{session: session} do
    assert session
      |> visit("/users")
      |> click_link("Create new account")
      |> fill_in("Name", with: "Billy Joel")
      |> fill_in("Email", with: "[email protected]")
      |> fill_in("Password", with: "password")
      |> click_on("Create new user")
      |> click_link("New Project")
      |> fill_in("Name", with: "Senior Class Air Quality")
      |> click_on("Submit")
      |> click_link("Senior Class Air Quality")
      |> click_link("New Location")
      |> click_on("Submit")
      |> find(".alert-danger p")
      |> text == "Oops, something went wrong! Please check the errors below."

    assert session
      |> fill_in("Name", with: "A new location")
      |> select("Type", option: "office")
      |> click_on("Submit")
      |> find(".alert-info")
      |> text == "Location created successfully."

    assert session
      |> find("td", count: 3)
      |> List.first
      |> text == "A new location"
  end

  test "a user can edit a location", %{session: session} do
    project = Factory.project()
    Factory.location(%{project_id: project.id})

    assert session
      |> visit("/users")
      |> click_link("Create new account")
      |> fill_in("Name", with: "Billy Joel")
      |> fill_in("Email", with: "[email protected]")
      |> fill_in("Password", with: "password")
      |> click_on("Create new user")
      |> click_link("New Project")
      |> fill_in("Name", with: "Senior Class Air Quality")
      |> click_on("Submit")
      |> click_link("Senior Class Air Quality")
      |> click_link("New Location")
      |> fill_in("Name", with: "A new location")
      |> select("Type", option: "office")
      |> click_on("Submit")
      |> click_link("Edit location")
      |> fill_in("Name", with: "different name")
      |> select("Type", option: "home")
      |> click_on("Submit")
      |> find(".alert-info")
      |> text == "Location updated successfully."

There is a lot of duplication there. How can I use the setup block to consolidate some of this code?

Upvotes: 1

Views: 2209

Answers (1)

nietaki
nietaki

Reputation: 9018

The setup and setup_all functions run the exact same setup code for all the tests in the module and they're the most useful if you need the exact same setup to be performed for all of them. An example of this would be setting up mock services your application depends on.

In your case it seems like you have some of the same steps, but as you add in more tests, there wouldn't necessarily be a common setup for all of them. You can however refactor the individual conceptual steps the tests are composed of into a separate module that you'd use in all the integration tests:

defmodule TestHelpers do
  # wallaby imports here

  def create_user(session, name, email, password) do
    session
      |> visit("/users")
      |> click_link("Create new account")
      |> fill_in("Name", with: name)
      |> fill_in("Email", with: email)
      |> fill_in("Password", with: password)
      |> click_on("Create new user")
  end

  def create_project(session, name) do
    session
      |> click_link("New Project")
      |> fill_in("Name", with: name)
      |> click_on("Submit")
  end

  def create_location(session, type), do: _modify_location("New Location", session, type, name)

  def edit_location(session, type), do: _modify_location("Edit location", session, type, name)

  defp _modify_location(link_text, session, location_type, location_name) do
    session = session
      |> click_link(link_text)

    session = case location_name do
      nil -> session
      x when is_binary(x) -> fill_in(session, "Name", with: location_name)
    end

    session = case location_type do
      nil -> session
      x when is_binary(x) -> select(session, "Type", option: location_type)
    end

    session
      |> click_on("Submit")
  end
end

Now the tests consist only of the helpers defined in TestHelpers and the assertions:

defmodule Test do
  import TestHelpers

  @project_name "Senior Class Air Quality"

  # aliases, uses, imports here
  test "a user can create a location", %{session: session} do
    session = session
      |> create_user("Billy Joel", "[email protected]", "password")
      |> create_project(@project_name)
      |> click_link(@project_name)
      |> create_location(nil, nil)

    assert session
      |> find(".alert-danger p")
      |> text == "Oops, something went wrong! Please check the errors below."

    session = session
      |> create_location("A new location", "office")

    assert session
      |> find(".alert-info")
      |> text == "Location created successfully."

    assert session
      |> find("td", count: 3)
      |> List.first
      |> text == "A new location"
  end

  test "a user can edit a location", %{session: session} do
    project = Factory.project()
    Factory.location(%{project_id: project.id})

    session = session
      |> create_user("Billy Joel", "[email protected]", "password")
      |> create_project(@project_name)
      |> click_link(@project_name)
      |> create_location("office", "A new location")
      |> modify_location("home", "different_name")

    assert session
      |> find(".alert-info")
      |> text == "Location updated successfully."
  end
end

You could also similarly extract the alert messages assertions to turn them into one-liners, which should make each of the tests very readable and robust if, for example, a link text or a div class changes - you would just make the change in the helpers.

Of course if you find yourself having a repeated multi-step setup in the tests, you can have it as a helper function and use it directly:

def standard_setup(session) do
  session
    |> create_user("Billy Joel", "[email protected]", "password"
    |> create_project(@project_name)
    |> click_link(@project_name)
    |> create_location("office", "A new location")
end

Upvotes: 1

Related Questions