lpil

lpil

Creator of Gleam

There’s no clean up to do, Ecto handled that automatically the same way as with factories.

You don’t need to write any setup or teardown code with fixtures, while with factories you write the setup.

0
Post #11
halostatue

halostatue

Something like that might work, yes. There’s a bit more work to it than just that, because Rails fixtures also give you a way to refer to the records from your tests. I’ve got some ideas, but no time to actually build this out, but I think it would entirely be possible to get something like Rails fixtures working with Ecto.

I’d probably avoid using YAML (to avoid an unusual dependency), but don’t have a better format offhand (JSON5 would be useful, but I don’t think that Jason can parse JSON5) except maybe .exs files that are supposed to produce an array of maps.

0
Post #12
LostKobrakai

LostKobrakai

That does setup for each test though. Not once for the whole test suite run.

0
Post #13
ityonemo

ityonemo

depends on where you call it. It’s in test/support, indicating that it’s compiled before test_helper.exs, so you could call it there. The fun is just there for a wee bit of flexbility. You’d be expected to have more than one object in there (as indicated by use of flat_map)

0
Post #14
halostatue

halostatue

The thing is, it’s ultimately not an either/or.

In some cases, it’s better to use direct configuration—usually when you’re testing what happens when your table is empty or you want it empty to make it easier to reason about what changed. When you’re using fixtures, you’d truncate the table(s) in question and create new database records as you are currently talking about. It’s an extra step, but a small one.

I’ve got code that requires that data in ~6 tables are set up with the correct relationships—and I run ~10 tests on that code. With fixtures that data is configured once and is then available for all ten tests. With per-test configuration, most of your test is configuration, not assertion. (Yes, you can do that in a setup block in describe; there are cases when you want that data available for tests that don’t fit into that describe without repeating yourself.)

You can do the same with factories, but factories hide the complexity behind typically one function call (e.g., if you need to set up a user, credentials, and profile record for each user, your create(:user) factory function might actually create three records behind the scenes and you don’t really know/remember.

Done properly, fixtures let you set up a minimal amount of meaningful data with no ceremony and explicit configuration. It usually far better reflects the state of your application’s database will be most of the time (because your database isn’t going to be empty for long).

Pure functions can and should be tested without touching the database. Using fixtures when you have any complexity to your data at all is going to be far easier to reason about than factories or per-test (or per-group) setup.

0
Post #15
lpil

lpil

Creator of Gleam

That’s right, all tests start with the same dataset which is inserted at the start of the suite. Ecto rolls back changes after each test, it does not truncate the database.

1
Post #16
tcoopman

tcoopman

If I’m reading everything correctly it seems that the only real difference between factories and fixtures is that fixtures are just the “real data” and factories go through some domain logic.
The other differences mentioned above seem things you can do with both kind of data creation.

For me this means that you probably want to use both factories and fixtures depending on what kind of test you’re writing. A factory adds more complexity because you go through the domain logic, but this prevents you from creating invalid data. A fixture is more standalone, so easier to reason about, but you have the risk that you have incorrect fixtures.

2
Post #17
josevalim

josevalim

Creator of Elixir

I will add some cents to the discussion that I haven’t seen mentioned yet. Below, I will be talking about database-backed fixtures (and not necessarily fixtures as a whole):

  1. One of the downsides of fixtures is that it is shared data across all of your tests. So sometimes you will change your fixtures, because you need new data to be used in some new tests, and other tests may now fail. This gets worse if projects define a large amount of fixture data. The correct approach, as mentioned earlier, is to define fixtures for a basic feature set that will be shared across all tests.

  2. The particular fixtures implementation in Rails caused some issues because referential integrity and data validations are disabled or not really used in Rails, so you could easily end-up with invalid data or data that would never exist in the database through the regular application workload.

  3. It is actually super straight-forward to have fixtures in Ecto: just write to the database in your test_helper.exs before you start the SQL sandbox.

Finally, a summary that was given to me a long time ago that explains when to use fixtures vs factories well is:

  1. Use fixtures for the setup data (i.e. the data you need to define before you can start writing your test)
  2. Use factories for the data under test, especially because you want the data being tested close to the test itself

In my opinion, factories are actually easier to get started as they require less discipline (with bigger costs in the long term), while fixtures are really easy to mess up at the beginning (but pay off if well-structured).

16
Post #18
halostatue

halostatue

Spot on, Jose. I really came to love the Rails fixtures for their speed, and the referential integrity stuff was only slightly annoying for me (I always use FKs at the database level, so the only time we ran into any sort of problem with this was with polymorphic relations, which we used factory-like functions for).

If I ever make an Ecto.Fixtures-type library, it’ll be set up to work against the database tables directly as opposed to Ecto schema modules. That would force you to think in terms of your underlying data model. The hard part is going to be, as you say, the referential integrity part, and then figuring out a way to make sure that the inserted data is readily available using similar naming conventions as the Rails ones. Maybe an Agent that stores {table, name} => pk values so that you could say something like Repo.get(User, Ecto.Fixtures.id("users", "luke")).

But that’s for a time when I, well, actually have time.

0
Post #19
josevalim

josevalim

Creator of Elixir

I think using schemas are actually fine because they are a simply mappers without any “smartness” to them and Ecto already pushes users to keep their data layers sane.

0
Post #20