Want to speed up your test suite? Reduce the number of objects persisted to the database. With Factory Bot, this is really easy; instead of using build
or create
to instantiate your models with data, use build_stubbed
!
build_stubbed
is the younger, more hip sibling to build
; it instantiates and assigns attributes just like build
, but that’s where the similarities end. It makes objects look like they’ve been persisted, creates associations with the build_stubbed
strategy (whereas build
still uses create
), and stubs out a handful of methods that interact with the database and raises if you call them. This leads to much faster tests and reduces your test dependency on a database.
For example, let’s say we have an OrderProcessor
that accepts instances of Order
and CreditCard
. It hits the Braintree API and returns a boolean value for if the charge actually happened.
class OrderProcessor
def initialize(order, credit_card)
@order = order
@credit_card = credit_card
end
def process
charge_successful? charge_customer
end
private
def charge_successful?(result)
# processes the result to determine if the result is valid,
# operating on the order and credit_card instance variables to
# add errors, send emails, or track Braintree's transaction id
end
def charge_customer
# runs Braintree::Customer.sale() with the appropriate options
end
end
None of this code hits the database. This allows us to write tests like:
describe OrderProcessor do
let(:transaction_id) { '1234' }
let(:order) { build_stubbed(:order) }
let(:credit_card) { build_stubbed(:credit_card) }
subject { OrderProcessor.new(order, credit_card) }
context 'when the Braintree result is valid' do
before do
MockBraintree.stub_successful_customer_sale(transaction_id: transaction_id)
end
it 'assigns the transaction id to the order' do
subject.process
order.transaction_id.should == transaction_id
end
it 'returns true for #process' do
subject.process.should be
end
it 'does not assign any errors to the credit card' do
subject.process
credit_card.errors.should be_empty
end
end
context 'when the Braintree result is invalid' do
before do
MockBraintree.stub_unsuccessful_customer_sale
end
it 'does not assign the transaction id to the order' do
subject.process
order.transaction_id.should be_nil
end
it 'returns false for #process' do
subject.process.should_not be
end
it 'assigns errors to the credit card' do
subject.process
credit_card.errors.should_not be_empty
end
end
end
Instead of creating twelve different records (at the minimum - if any of these factories have associations, you introduce more multipliers), we create none. This keeps the spec blazing fast.
Properly factored code should be small and concise. Code should typically depend
less on the state of the data in relation to the database and more on its
state in relation to other objects. In the above example, OrderProcessor
only handles dealing with Braintree and dealing with its errors; it does not
care about how this data is displayed to the user. That’s left for the
integration tests, which should be hitting the database.
Although it’s not useful in every situation (there are cases where you’ll want
data to exist, like when you’re testing uniqueness constraints or scopes),
build_stubbed
should be your go-to FactoryBot method over build
or
create
. Everyone running your test suite (yes, even yourself) will thank you.
Disclaimer:
Looking for FactoryGirl? The library was renamed in 2017. Project name history can be found here.