{"id":5007,"date":"2016-01-21T07:00:08","date_gmt":"2016-01-21T09:00:08","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=5007"},"modified":"2016-01-28T12:34:10","modified_gmt":"2016-01-28T14:34:10","slug":"writing-acceptance-tests-in-phoenix","status":"publish","type":"post","link":"http:\/\/blog.plataformatec.com.br\/2016\/01\/writing-acceptance-tests-in-phoenix\/","title":{"rendered":"Writing Acceptance tests in Phoenix"},"content":{"rendered":"
Acceptance testing seems to be in its first steps in the Elixir ecosystem, but there are already some cool libs that can help us out to do it. I’m going to show you how we did it with Hound<\/a>.<\/p>\n In this blog post, we’ll write a few acceptance tests for an expenses report listing page, where we’ll interact with the report and some form elements.<\/p>\n To make possible to interact with the elements on the page, we’ll need a web browser driver. Hound accepts First we’ll need to add Hound into our dependencies, so add the following line in your Make sure it’ll start during the test suite runtime. To do this we’ll need to add We’ll be using Take a look at this doc from Hound resources<\/a> to check if you’d like different configs.<\/p>\n We’ll also need to set the server config in our That should do it! Before writing our first test, let’s define an There are a few lines that are worth commenting:<\/p>\n Let’s test!<\/p>\n We’re testing a simple list of expenses from a city (that example was extracted from an app we have been working on, but its scope was reduced so we can follow the steps more easily).<\/p>\n Take a look at its template code:<\/p>\n We’ll need a new test file, let’s put it at We’ll be using three private functions to help us making assertions and interacting with elements in our test file. The first one, To interact with an element, you’ll always need to find the element on the page and for this, you need to know Hound’s page helpers<\/a>. I’ve noticed that we ended up using Since this test is about the City resource, we’ve created just this city and navigated to it directly on the setup, since this would be a requirement for all the tests in this file, and shared it with all the tests through the setup context.<\/p>\n Navigation<\/a> is another important Hound module. It will help us go through our app easily and get info about the page, like the Now that we’re on the page, we’ll be interacting with the form, by finding elements that are within it and filling or selecting values on them.<\/p>\n The module responsible for these tasks is Element<\/a>. It has very useful functions, like In the previous example, the interactions with the form ended with One of the things I’ve paid a lot of attention during this experience was the test suite runtime. As expected, it can get slow with acceptance tests. The original app is still really tiny and before adding the acceptance tests, the runtime was:<\/p>\n After including two tests (but with more interactions than the ones presented), it was noticeable the test suite became slower. It tripled the runtime.<\/p>\n This effect is actually expected. We know that acceptance tests are expensive and that they should be a small portion of your test pyramid<\/a><\/p>\n There are a few things that can make acceptance tests faster:<\/p>\n In my experience Phantom.js considerably faster than the other options, I recommend you to go with it.<\/p>\n<\/li>\n<\/ul>\n You will definitely need to write some acceptance tests, but give preference to controller tests in most scenarios and use acceptance tests for important flows of your app (take a look at the user journey concept<\/a>, that can give you some good insights).<\/p>\n Currently, Hound doesn’t execute the browser automatically during tests. You’ll need to start it; otherwise, your tests will fail. There may be some workarounds to achieve it, if you’re on OS X, you can run Phantomjs as a service<\/a>.<\/p>\n I really enjoyed playing with Hound, and I found very simple to work with it. Also, I see it as a potential project if you’re considering contributing to an Open Source Project.<\/p>\n I hope this post was useful and gave you some ideas of how to write acceptance tests with Elixir and Phoenix. If you have any questions or suggestions, I’m all ears (or eyes).<\/p>\n Which tool have you recently found to be useful when writing tests in Elixir?<\/em><\/p>\n Acceptance testing seems to be in its first steps in the Elixir ecosystem, but there are already some cool libs that can help us out to do it. I’m going to show you how we did it with Hound. In this blog post, we’ll write a few acceptance tests for an expenses report listing page, … \u00bb<\/a><\/p>\n","protected":false},"author":38,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[130,143,245,96],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/5007"}],"collection":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/38"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=5007"}],"version-history":[{"count":23,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/5007\/revisions"}],"predecessor-version":[{"id":5046,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/5007\/revisions\/5046"}],"wp:attachment":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=5007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=5007"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=5007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}chrome_driver<\/code>,
firefox<\/code>,
phantomjs<\/code> and
selenium<\/code>. My choice is
phantomjs<\/code> because I want to use a headless browser (I don’t want the driver to open a browser during the execution of the test suite).<\/p>\n
Setup<\/h2>\n
mix.exs<\/code>:<\/p>\n
{:hound, \"~> 0.8\"}\n<\/code><\/pre>\n
Application.ensure_all_started(:hound)<\/code> before
ExUnit.start<\/code> in our test helper:<\/p>\n
Application.ensure_all_started(:hound)\nExUnit.start\n<\/code><\/pre>\n
phantomjs<\/code> as our web driver. Make sure it’s properly installed and that you can start it<\/a> with
phantomjs --wd<\/code>. To configure it, add this to the
config.exs<\/code> file:<\/p>\n
config :hound, driver: \"phantomjs\"\n<\/code><\/pre>\n
config\/test.exs<\/code>to
true<\/code>.<\/p>\n
config :my_app, MyApp.Endpoint,\n http: [port: 4001]\n server: true\n<\/code><\/pre>\n
IntegrationCase<\/code> module, similar to the
ModelCase<\/code> and
ConnCase<\/code> provided by Phoenix, which will include all functionality we need to write our integration tests. Create the
test\/support\/integration_case.ex<\/code> file and add the following content:<\/p>\n
defmodule MyApp.IntegrationCase do\n use ExUnit.CaseTemplate\n\n using do\n quote do\n use Hound.Helpers\n\n import Ecto.Model\n import Ecto.Query, only: [from: 2]\n import MyApp.Router.Helpers\n\n alias MyApp.Repo\n\n # The default endpoint for testing\n @endpoint MyApp.Endpoint\n\n hound_session\n end\n end\n\n setup tags do\n unless tags[:async] do\n Ecto.Adapters.SQL.restart_test_transaction(MyApp.Repo, [])\n end\n\n :ok\n end\nend\n<\/code><\/pre>\n
\n
use Hound.Helpers<\/code> will include the helpers necessary for us to interact with our interface (take a look at the docs and explore them a little, they’ll be very helpful<\/a>);<\/li>\n
hound_session<\/code> will make sure that Hound will start and closes its session during the test execution;<\/li>\n
import MyApp.Router.Helpers<\/code> will include helpers so we can manipulate routes and URLs.<\/li>\n<\/ul>\n
Exercise<\/h2>\n
<div>\n <%= form_for @conn, city_expense_path(@conn, :index, @city.id), [as: :q, method: :get], fn f -> %>\n <div>\n <label for="q_status">Status<\/label>\n <%=\n select f, :status, [{"Paid", "paid"}, {"Cancelled", "cancelled"}], prompt: "All" %>\n <\/div>\n\n <div>\n <label for="q_supplier">Supplier<\/label>\n <%= text_input f, :supplier %>\n <\/div>\n\n <%= submit "Submit" %>\n <% end %>\n<\/div>\n\n<table>\n <thead>\n <th>ID<\/th>\n <th>Status<\/th>\n <th>Supplier<\/th>\n <th>Value<\/th>\n <th>Date<\/th>\n <\/thead>\n <tbody>\n <%= for expense <- @expenses do %>\n <tr>\n <td><%= expense.id %><\/td>\n <td><%= expense.status %><\/td>\n <td><%= expense.supplier.name %><\/td>\n <td><%= expense.value %><\/td>\n <td><%= expense.date %><\/td>\n <\/tr>\n <% end %>\n <\/tbody>\n<\/table>\n<\/code><\/pre>\n
test\/integration\/expenses_list_test.exs<\/code>. To use Hound\u2019s facilities, we’ll need to use the
IntegrationCase<\/code> module that we have previously created.<\/p>\n
defmodule MyApp.ExpenseListTest do\n use MyApp.IntegrationCase\n\nend\n<\/code><\/pre>\n
expense_on_the_list<\/code>, will check if a given expense, that is represented by the Ecto model called
MyApp.Expense<\/code>, is in there. The second function is just a helper for getting the expenses list and the third will help us interact with a select input within a form.<\/p>\n
defmodule MyApp.ExpenseListTest do\n use MyApp.IntegrationCase\n\n # ...\n\n defp expense_on_the_list(expense, list) do\n list\n |> visible_text\n |> String.contains?(expense.id)\n end\n\n defp expense_list_items do\n find_element(:tag, \"tbody\")\n |> find_all_within_element(:tag, \"tr\")\n end\n\n defp select_status(form, status) do\n form\n |> find_within_element(:id, \"q_status\")\n |> input_into_field(status)\n end\nend\n<\/code><\/pre>\n
find_element<\/code> and
find_all_within_element<\/code> most of the time to find the elements on the page or in a context (i.e inside a previously found element).<\/p>\n
setup do\n city = insert_city!(%{name: \"Winterfell\"})\n\n navigate_to(\"\/cities\/#{city.id}\/expenses\")\n\n {:ok, city: city}\nend\n<\/code><\/pre>\n
current_path()<\/code> function that returns the path we’re navigating on that moment.<\/p>\n
test \"filter by supplier\", %{city: city} do\n supplier = insert_supplier!(%{name: \"Ned Stark\"})\n supplier_b = insert_supplier!(%{name: \"Bell Tower Management\"})\n expense = insert_expense!(%{supplier: supplier, city: city, status: \"paid\"})\n insert_expense!(%{supplier: supplier_b, city: city, status: \"paid\"})\n\n search_form = find_element(:tag, \"form\")\n search_form\n |> find_within_element(:id, \"q_supplier\")\n |> fill_field(\"Ned\")\n\n submit_element(search_form)\n\n\n items = expense_list_items\n assert length(items) == 1\n assert expense_on_the_list(expense, items)\nend\n<\/code><\/pre>\n
fill_field<\/code> we used above. All of its functions require an element.<\/p>\n
submit_element<\/code>, but if we need any other action on it after this, we would need to re-assign it (otherwise, we’ll get a
** (RuntimeError) Element does not exist in cache<\/code> error), like in the following example:<\/p>\n
test \"filter by statuses\", %{city: city} do\n supplier = insert_supplier!(%{name: \"Jon Snow\"})\n\n cancelled_expense = insert_expense!(%{supplier: supplier, city: city, status: \"cancelled\"})\n paid_expense = insert_expense!(%{supplier: supplier, city: city, status: \"paid\"})\n\n search_form = find_element(:tag, \"form\")\n select_status(search_form, \"Cancelled\")\n\n submit_element(search_form)\n\n items = expense_list_items\n assert length(items) == 1\n assert expense_on_the_list(cancelled_expense, items)\n\n search_form = find_element(:tag, \"form\")\n select_status(search_form, \"Paid\")\n submit_element(search_form)\n\n items = expense_list_items\n assert length(items) == 1\n assert expense_on_the_list(paid_expense, items)\nend\n<\/code><\/pre>\n
Verify<\/h2>\n
Runtime<\/h3>\n
Finished in 0.6 seconds (0.5s on load, 0.1s on tests)\n23 tests, 0 failures, 2 skipped\n<\/code><\/pre>\n
Finished in 1.8 seconds (0.5s on load, 1.2s on tests)\n25 tests, 0 failures, 2 skipped\n<\/code><\/pre>\n
\n
Web driver server execution<\/h2>\n
Teardown<\/h2>\n
\n
\n<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"