I'm attempting to speed up some tests for a Rails controller and a bottleneck has to do with large numbers of objects being created and persisted to the database. I'm attempting to replace most of those create
calls with build
calls to address this.
Running Rails 5.1
, and using MiniTest 5.10.3
, with FactoryBot 5.0.2
.
I'm attempting to go from this
@user = create(:user)
@item1 = create(:item)
@item1 = create(:item)
@transaction1 = create(:transaction, buyer: @buyer, item: @item1)
@transaction2 = create(:transaction, buyer: @buyer, item: @item2)
In this application item
represents a sellable object, user
represents a purchaser and transaction
is the object that creates the two. The User
class also has an association added to it transaction_checkout_items
which returns all Transaction
items which are in a state where the purchase can be completed.
So, with each test we're creating a myriad of objects and saving them all to the database. It's slow but it works. Still, I want it to be faster, so I've tried replacing the existing setup with something like this:
@user = create(:user)
def build_transaction_checkout_items(user, item)
user.transaction_checkout_items.build(attributes_for(:transaction,
buyer: user,
sale_price: item.sale_price,
item: item))
end
@item1 = build_stubbed(:item)
@item2 = build_stubbed(:item)
@transaction1 = build_transaction_checkout_items(@buyer, @item1)
@transaction2 = build_transaction_checkout_items(@buyer, @item2)
This seems to work as long as I'm in the test. If I drop a binding in my test and check the objects @user
returns the user object, @user.transaction_checkout_items
returns a Transaction::ActiveRecord_Associations_CollectionProxy
object containing all my associated transactions, and the individual transactions have their associated items attached. However, If I put a binding.pry
into the controller method which actually does the work, and look at the User
object I see the correct one, but user.transaction_checkout_items
now returns an empty Transaction::ActiveRecord_Associations_CollectionProxy
object with nothing in it. Essentially the associations vanish, and this makes sense to me as the controller is pulling the User
object from the database and going to work on it, and this new object is missing the associations. I've considered trying to stub out an any_instance
method on the User
class so that whenever #transaction_checkout_items
is called it returns the collection of Transaction
objects but I don't see any way to create a new ::ActiveRecord_Associations_CollectionProxy
object. I can't simply use an array or other collection for this as there are methods on the ::ActiveRecord_Associations_CollectionProxy
that need to be called for the controller logic to work.
So here I am on a Friday blocked. Is my idea of stubbing transaction_checkout_items
on any User
instance a good one, and if so how do I do it? Or is there an alternate strategy anyone can suggest that will allow the MiniTest stubbed associations to persist and be available when the controller code runs?
Is my idea of stubbing transaction_checkout_items on any User instance a good one, and if so how do I do it?
Stubbing out ActiveRecord methods is almost always a bad idea. It will couple your tests heavily to the implementation and potentially will make it difficult to update Rails / ActiveRecord as if anything changes in the framework your tests start breaking. There might be also lots of funny side effects you haven't thought about.
The question is also what do you actually want to test? If you start stubbing out these methods, what are you testing? In a controller / integration test, I would expect you want to test to fetch the correct records from the database.
Using build vs. create to improve test performance is a good trick but, as you already discovered, is only valuable if you use the same objects in the test. This is unfortunately not possible for integration tests and you need / should persists the records.
Or is there an alternate strategy anyone can suggest that will allow the MiniTest stubbed associations to persist and be available when the controller code runs?
I would think about why this is slow and if this is actually really a problem. How long does your test & whole test suite run or is this just a premature optimisation?
If the reason it's slow is solely because you need to create a lot of test data you could use fixtures or seed your database instead. This would both be faster than using FactoryBot although brings different issues (eg MysteryGuest )
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.