beam-community/ex_machina

Reusing same factory in a different factory (through association)

itssasanka opened this issue · 2 comments

Hi, I have my schema like this:

  schema "tokens" do
    belongs_to :user, User
    field :access_token, :binary_id
    field :status, :integer, default: 1
    timestamps()
  end
schema "users" do
    has_many :tokens, Token
    field :email, :string
    field :first_name, :string
    field :last_name, :string
    field :password, :string
    timestamps()

I have a tokens factory like this (in a separate file):

defmodule Sample.TokenFactory do
  defmacro __using__(_opts) do
    quote do
      def token_factory do
        %Sample.Auth.Token{
          access_token: "69586483-82b8-45a1-b4d0-10456d5ad04c",
          status: 1,
          user: build(:user)
        }
      end

      def second_token_factory do
        %Sample.Auth.Token{
          access_token: "92fab9fc-7bc4-42ec-a3c3-7413cc37a8f2", 
          status: 1,
          user: build(:user)
        }
      end
    end
  end
end

When I use it in my tests, like this,

insert(:token)
insert(:second_token)

The second insert tries to insert the user again, instead of reusing existing user factory created by the first insert. I've searched all the documentation, pardon me if I am missing something, but what's going wrong here?

Thanks very much in advance!

Hi @itssasanka, having build(:user) in each factory's definition basically means you'll be generating a new user struct for each Sample.Auth.Token struct when you use build(:token) and build(:second_token).

In order words, if you were to do build(:token), you could think of it as generating something like this,

%Sample.Auth.Token {
  access_token: "69586483-82b8-45a1-b4d0-10456d5ad04c",
  status: 1, 
  user: %Sample.User { 
     # whatever fields you have in your user factory
  }
}

When you use insert, it will insert both the token and the user.

If you want to use the same user for both tokens you have to pass it as an argument into the token factories,

user = insert(:user)
insert(:token, user: user)
insert(:second_token, user: user)
user = insert(:user)
insert(:token, user: user)
insert(:second_token, user: user)

Thank you @germsvel for the response, I had the above in mind already, it is an "okay" solution from my point of view especially because if there are multiple associations, then that involves creating related records manually and passing them around like this beforehand, as opposed to something as simple as

insert(:user)
insert(:user_comment1)
insert(:user_comment2)
insert(:user_profile)
blah...

The rails Factory girl is able to make use of these existing factories like this and keep the tests really separate from having to deal with building factories which is cleaner IMHO.
Thank you once again though