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
You might wanna play with module attributes
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.
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
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.
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 Thanks