woohaaha
Hi all,
How do you write your context tests (create_thingy(attrs)) when there are a lot of associations that need to be created first?
Here is a contrived example. Let’s says I have a Door model that I’d like to test the color, material, price, etc..
A Door cannot be created without having to first create a Wall
A Wall cannot be created without having to first create a House
A House cannot be created without having to first create a Blueprint
A Blueprint cannot be created without having to first create a Contract
A Contract cannot be created without having to first create a Client
A Client cannot be created without having to first create a Prospect
and on and on… ![]()
I have a method in my Building context that sort of looks like this:
def create_door(attrs \\ %{}) do
%Door{}
|> Door.changeset(attrs)
|> Repo.insert()
end
Ideally in my test I’d like to write something like:
{:ok, door} = create_door(%{price: 1_234, material: "wood", color: "white"})
assert door.color == "white"
However I cannot create the Door without passing in a wall_id and since that field cannot be null I’d need to create a Wall. The creation process then has to go all the way up to … create_user. Surely I’ve missed something huge I can do that will still make me confident in the production code, but won’t make testing difficult.
Any tips or suggestions are greatly appreciated.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 3- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
Mpanarin
I am not very familiar with the approach in phoenix specifically, as I am pretty new to elixir. But this situation is very common in any other language.
I come from python world where this can be generally mitigated by 2 approaches:
With second approach you will have to do a huge effort of creating this factories if your codebase is fairly big already. But they are easier to maintain as well as way more flexible for your tests )
LostKobrakai
One way to go about it is separating the door from the relationship to walls. Instead of
door <- wallhavedoor <- wall_door -> wallin the db. With a unique constraint onwall_door.door_idyou can make sure a way still only belongs to a single wall at most. Having done that on the db level you can do the same separation on the domain level: instead of justcreate_dooryou’ll also have e.g.create_door_for_wall. Once that is done you can test a door without needing a wall.On a more concrete example. I’ve recently did contacts handling in an app, which was initially scoped to tenants. For less coupling I did introduce the concept of a contacts directory. I can easily create a directory for each tenant to use, but I don’t need a tenant, as they’re only coupled by name. I can easily create directories in tests without needing tenants and if I ever need contacts scoped to something else than tenants that’s fine as well.
tfwright
One thing you can do is to define helper functions in your test_helper that you can use with ExUnit’s
setupcallback.If there’s not one in your context, you can define a test only API that handles all the setup for you:
Instead of calling
Building.create_doordirectly in your test you’d do something like this:Or you may prefer to keep your setup functions more atomic and explicit and use context to store the dependencies:
Then you just need to specify each dependency in the setup, which is pretty manageable even with the large number in your contrived example.