4

I'd like to test how a function changes something in the database. I'm struggling with an ExUnit equivalent of the following ActiveSupport::TestCase test case:

test "creates a database record" do
  post = Post.create title: "See the difference"
  assert_difference "Post.published.count" do
    post.publish!
  end
end

The RSpec version is more elegant and, because of its use of lambdas, something I thought was portable to Elixir/ExUnit.

it "create a database record" do
  post = Post.create title: "See the difference"
  expect { post.publish! }.to change { Post.count }.by 1
end

Is there a more elegant (read: functional) way to do it than this:

test "creates a database record", %{conn: conn} do
  records_before = count_records
  post(conn, "/articles")
  records_after  = count_records

  assert records_before == (records_after - 1)
end

defp count_records do
  MyApp.Repo.one((from a in MyApp.Article, select: count("*"))
end
Carsten
  • 531
  • 4
  • 15
  • 1
    More functional way to detect a `change` (read “to detect an object mutation”) sounds a bit weird to me :) There is no state, objects are all immutable, so the most functional way would be the one you’ve proposed. – Aleksei Matiushkin Sep 28 '16 at 10:30

1 Answers1

4

You can use macros to get something close to the TestUnit and RSpec examples from Ruby:

defmacro assert_difference(expr, do: block) do
  quote do
    before = unquote(expr)
    unquote(block)
    after_ = unquote(expr)
    assert before != after_
  end
end

defmacro assert_difference(expr, [with: with], do: block) do
  quote do
    before = unquote(expr)
    unquote(block)
    after_ = unquote(expr)
    assert unquote(with).(before) == after_
  end
end

test "the truth" do
  {:ok, agent} = Agent.start_link(fn -> 0 end)

  assert_difference Agent.get(agent, &(&1)) do
    Agent.update(agent, &(&1 + 1))
  end

  {:ok, agent} = Agent.start_link(fn -> 0 end)

  assert_difference Agent.get(agent, &(&1)), with: &(&1 + 2) do
    Agent.update(agent, &(&1 + 2))
  end
end

But I wouldn't use it unless it's going to be used a lot or else this would only make the code harder to follow for everyone (possibly) except the author. If you do use it though, you might want to move it to a different module and import that in your test modules.

Dogbert
  • 212,659
  • 41
  • 396
  • 397