pinksynth

pinksynth

Tip: Using Ecto transactions in tests

Whenever tests have to interact with an Ecto.Repo, sometimes it’s necessary to test a few different branches or paths that data can take. This can be tedious with a database and may result in “resetting” records so they may be re-tested. Here’s a crude example of “resetting” some data to continue with a test:

test "vaccinate_dog/1 vaccinates dog and creates vax record with the corresponding name" do
  dog = dog_fixture()
  
  # Perform assertions about vaccinations
  assert :ok = vaccinate_dog(dog, "bordetella")
  assert %{id: dog_id, vaccinated: true} = dog = Repo.one!(Dog)
  assert %{dog_id: ^dog_id, type: "bordetella"} = Repo.one!(Vax)

  # Reset state for the dog
  Repo.delete_all(Vax)
  dog |> Ecto.Changeset.change(%{vaccinated: false}) |> Repo.update!()

  # Performmore  assertions about vaccinations
  assert :ok = vaccinate_dog(dog, "rabies")
  assert %{id: dog_id, vaccinated: true} = dog = Repo.one!(Dog)
  assert %{dog_id: ^dog_id, type: "rabies"} = Repo.one!(Vax)
end

By using Repo.transaction/2, it’s easy to simply roll back a transaction rather than “resetting” anything (assuming that side-effects are not a concern for the purposes of the test.

test "vaccinate_dog/1 vaccinates dog and creates vax record with the corresponding name" do
  dog = dog_fixture()

  for vaccine <- ["rabies", "bordetella"] do
    in_transaction(fn ->
      assert :ok = vaccinate_dog(dog, vaccine)
      assert %{id: dog_id, vaccinated: true} = dog = Repo.one!(Dog)
      assert %{dog_id: ^dog_id, type: vaccine} = Repo.one!(Vax)
    end)
  end
  
  defp in_transaction(fun) when is_function(fun, 0) do
    Repo.transaction(fn ->
      fun.()
      Repo.rollback(:ok)
    end)
  end
end

I know that it’s generally best to decouple the DB and the business logic as much as possible, but I have found this to be a useful way to succinctly cover more ground within Repo-related tests.

Most Liked

al2o3cr

al2o3cr

This is usually where I’d stop this test and write another one, TBH. Unless dog_fixture is very expensive to compute, I’d prefer to see duplication if testing with "rabies" vs "bordetella" exposes different behavior.

Also note there’s no support AFAIK for nested transactions, so this won’t work as expected if used with the sandbox-enabled test harness that’s generated by Phoenix.

Last Post!

pinksynth

pinksynth

Yeah, admittedly this is not the best example. My actual use case is a module which does some pretty complex query composition using “filters” that can be manipulated recursively by the end user and is generally in the “hard to test” world of high-CC code. I don’t recall the specifics of how our Repo is set up in tests but we use transactions like this quite a bit and they all seem to work fine :grimacing:

Where Next?

Popular in Guides/Tuts Top

dennisreimann
I wrote a guide for implementing Passwordless Authentication a.k.a. “Magic Login Links”: https://dennisreimann.de/articles/phoenix-passw...
New
benwilson512
Correct if me I’m wrong, as best I can tell there aren’t any reasons to use mix run --no-halt in production vs releases. The marginal val...
New
marcelo
I wrote a small article on how to use current_user with coherence on Phoenix. There are already a couple of blogposts about it but I thin...
New
anuragg
We just published a guide to automatic clustering in Elixir 1.9, with Mix releases and libcluster. The cluster automatically discovers n...
New
nelsonic
When we were figuring out how to use Phoenix LiveView we got stuck a few times. So in order to save other people time, we created a comp...
New
berts-4865
Here is a quick guide to uploading a file from the browser to DO spaces. It is crude, but will hopefully save sometime time and frustrat...
New
marcin
This post is intended to be a guide for others, I was running a remote debugger for the first time and appreciate feedback on how I could...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement