Fixture on tests load once and keep for all the tests

Hello,
I couldn’t find an answer to this:
I have a fixture that has some baseline data such as postal codes and cities (they are some hundreds) and since that is almost static data I want to load once and use that over the tests without having to load the fixture in several tests.
What is the best approach?
The setup function is executed once per test and I want to execute only once everytime I run “mix test”.
Does it makes sense or goes against the concurrency models? What is the approach?
Thank you

Hey @nuno84 :wave:

You might wanna play with module attributes :slightly_smiling_face:

defmodule PostalData do
  @postal_codes [1, 2, 3]

  def postal_codes, do: @postal_codes
end

@postal_codes gets compiled with the module, and it’s available without copy by different processes.

Hi again,
But in that sense I cannot make queries with joins and so on.
Imagine I want to make some georeference calculations. Maybe I can delegate them directly to the database.
But again, maybe I am not thinking in the right pattern.
Thank you

It’s not totally clear but it sounds like you’re trying to load this data from the database and it’s an expensive query?

One simple option is to just use setup_all to only load this once per test suite, rather than once per test. But if you find yourself using this across many test suites and want to make that even more efficient you could set something up in your DataCase or a custom test case module and cache it in :persistent_term. For example:

defmodule MyApp.DataCase do
  # ...
  setup tags do
    MyApp.DataCase.setup_sandbox(tags)
    %{geo_data: MyApp.DataCase.fetch_expensive_geo_data()}
  end
  
  def fetch_expensive_geo_data() do
    case :persistent_term.get(:test_case_geo_data, nil) do
      nil ->
        geo_data = MyApp.Geographies.perform_expensive_query()
        :persistent_term.put(:test_case_geo_data, geo_data)
        geo_data

      geo_data ->
        geo_data
    end
  end

Now when you use MyApp.DataCase your data is available in every test context as :geo_data but the query is only performed once per test run.

1 Like

The way I understood @nuno84’s question is that he has a fixture that inserts test data in the DB, and since many tests are using this one fixture, he would like to run it only once before the test suite starts as opposing to having each test run the fixture and then tear down the data when it ends. I hope this is the correct interpretation :slight_smile:

If you have this kind of “baseline” data that each test needs, you could seed the test database by adding a seeding step to your test task alias. For example, in your mix.exs:

defp aliases do
[
...
  test: ["ecto.create --quiet", "ecto.migrate --quiet", "run priv/repo/seed_with_testdata.exs", "test"]
...
]
end

Where seed_with_testdata.exs is the script that inserts the baseline data into your DB.
It’s important that your tests don’t try to modify this data, otherwise you’d might run into unexpected delays or even deadlocks when tests lock the DB rows for write and wait for each other.

1 Like

Hi again, and thank you all for your replies.
Trisolaran’s description is the most accurate for what I want to do and his solution seems to be the one I was looking for.
And with this answer you showed me one nice feature of how the test environment is setup for execution. Nice :smiley: Thanks

1 Like