A Factory is a class that allows you to easily create test data.
Even though a Factory is generally used for test data, you can also use it to seed your database with development data as mentioned in database setup.
Factories will live in your
spec/support/factories/ directory. Each factory will inherit from
and take the naming convention of the name of your model followed by
User model and
# spec/support/factories/post_factory.cr class PostFactory < Avram::Factory def initialize title "My Post" body "Test Body" end end
Each factory will have access to the associated model’s column fields. In the example
Post model has a
column title : String and
column body : String, then the factory will have
title method and
These methods take an argument of the default value you want to set for that factory.
class PostFactory < Avram::Factory def initialize title "Milk was a bad choice" body "Every post created from this PostFactory will default to these values" end end
Post will usually have more columns like
updated_at. We can omit
these since Avram will handle setting values on these for us. We can also omit any other fields
that are nilable by default.
Your model may have a unique constraint on a field like a
Post title, for example. In this case,
you can use the
sequence method to auto-increment a number on to your default value ensuring it will
class PostFactory < Avram::Factory def initialize title sequence("My new blog post") end end
The first time a
PostFactory is created, the
title will be set to
"My new blog post-1". The next time
one is created, the
title will be set to
"My new blog post-2", and so on.
Sequences always return a
String. For sequence type values on other types, you’ll need to implement those yourself.
When you create a Factory, you may want to have an association already set for you. In this case,
you’ll just use that association’s factory to set the
class PostFactory < Avram::Factory def initialize title sequence("post-title") body "blah" user_id UserFactory.create.id end end
Creating a Factory returns an instance of that model, which means you have access to all of the model methods.
Similar to SaveOperation, Factories also have
after_save callbacks. However, these methods are meant to be used within your factory’s
These can be great for dynamically setting an association.
class PostFactory < Avram::Factory def initialize title sequence("post-title") body "blah" before_save do if operation.user_id.value.nil? user_id UserFactory.create.id end end after_save do |new_post| 10.times do CommentFactory.create &.post_id(new_post.id).text(sequence("blah")) end end end end
As shown previously, a Factory has a
create method which saves the record to your database. A Factory is
essentially a fancy wrapper around SaveOperation.
Factories give you access to two helpful class methods
This will create a
Post, and store it in your database, then return the instance of
It will use the defaults you defined in your
initialize method of
post = PostFactory.create post.title #=> "post-title-1" post2 = PostFactory.create post2.title #=> "post-title-2"
When you need to override the defaults previously set, you’ll pass a block to
PostFactory.create do |factory| factory.title("Draft") factory.body("custom body") end
Thanks to Crystal’s short one-argument syntax, we can shorten this with
PostFactory.create &.title("Draft").body("custom body")
It may be common for you to need to create more than 1 test object at a time. For this, you can use
create_pair method to create 2 test objects!.
The big difference here is that
nil, and you can’t override the default data. Use this as “set it and forget it”.
Once you have your factories setup, and you’re ready to test, you’ll add your tests to your
# spec/post_spec.cr require "./spec_helper" describe Post do it "has 2 posts" do PostFactory.create_pair query = PostQuery.new.select_count query.should eq 2 end it "sets a custom post title" do post = PostFactory.create &.title("Custom Post") query = PostQuery.new.title("Custom Post") query.first.should eq post end end
If you’ve made a change to your data, you can call
reload to get the updated data.
it "updates a post title" do post = PostFactory.create &.title("Custom Post") SavePost.update!(post, title: "New Post Title") # The `post` still has the original data post.title.should eq "Custom Post" # Reloading returns a new model with the updated data updated_post = post.reload updated_post.title.should eq "New Post Title" end
Read up on reloading models for more information.