Overriding create method in rspec - ruby-on-rails

I'm trying to write specs for a method that uses the #create method which writes to the database which is causing issues because its being created with an object that's not saved to the database -- does anyone know how to override #create so it doesn't try to save to the database when running specs?
I get this error:
ActiveRecord::RecordNotSaved:
You cannot call create unless the parent is saved

As #Raghu pointed out, you need mocking/stubbing (see http://blog.firsthand.ca/2011/12/example-using-rspec-double-mock-and.html) to avoid actually running the create method. You probably don't want to test if create actually works. You are more likely testing that create is called on right time, on right model and with right options.

This is the place to use a Stub / Double (formerly, and still commonly, known as "Mocks")

Related

Maintianing ActiveRecord associations created using FactoryBot in controller test

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 (e.g. MysteryGuest)

factory_bot_rails stubbed models are not allowed to access the database ruby on rails

I am not that experienced in writing tests so apologize in advance if the question is not accurate enough, so I was writing some specs and suddenly started to receive stubbed models are not allowed to access the database, I realized that is due to a flow on a particular spec, something like book.update_column(:title, 'El principito'), so I looked around and seems like factory_bot_rails does not stub the response from update_column, the thing is that I need to use update_column because I don't want to trigger callbacks on this flow, any ideas on how to accomplish this?
(I manage to change a little the flow in order to be able to run the specs successfully but I don't like that approach)
My stack: rails 5.2.3, ruby 2.6.3, RSpec
Thx in advance 👍!
To update a object using Factory bot you need to use create in factories. Example:
FactoryBot.create(:book)
If you are creating with build_stubbed you will get this error.
FactoryBot.build_stubbed(:book)
I am assuming you have used the build_stubbed method to build an instance of the model.
so if you want to update it and don't want to save do it like this.
book.title = 'El principito'
And if you want to use update, save or update_column method then record must be persisted in the database. In this case instead of stubbing, create book record using the create method
create(:book, title: 'ABC')
And now you can use update, save and update-column methods

How to test a Rails Model Class method which calls a method on all members?

In my Rails 4 project I've got a model, EventGenerator with an instance method generate (which creates some records in the database), and a class method generate_all which is intended to be called by a Rake task and looks something like this:
def self.generate_all
all.each(&:generate)
end
I can think of several approaches to testing this (I'm using RSpec 3 and Fabrication):
Don't bother - it's too simple to require tests. DHH says "Don't aim for 100% coverage". On the other hand, this is going to be called by a rake task, so won't be regularly exercised: I feel like that's a good reason to have tests.
Create a couple of EventGenerator instances in the database and use any_instance.should_receive(:generate) as the assertion - but RSpec 3 now recommends against this and requires a fudge to make it work. This is a personal 'showcase project' so if possible I'd like everything to be best-practice. Also (DHH aside) shouldn't it still be possible to create fast model specs which don't touch the database?
Like 2, but stub out EventGenerator.all to return some instances without touching the database. But stubbing the class under test is bad, right? And fragile.
Don't worry about unit testing it and instead cover it with an integration test: create a couple of generators in the database, run the method, check what gets changed/created in the database as a result.
Change the implementation: pass in an array of instances. But that's just pushing the problem back by a layer, and a test-driven change which I can't see benefitting the design.
Since I can't really think of a downside for option 4, perhaps that's the answer, but I feel like I need some science to back that up.
I would actually not bother to test it (so your 1.) as the method is really trivial.
If you would like to have it under test coverage though I'd suggest you to use your 3. My reasons are as follows:
Your test for .generate_all just needs to assert that the method #generate gets call on every instance returned by .all. It this context the actual implementation of .all is irrelevant and can be stubbed.
Your tests for #generate should assert that the method does the right thing. If these tests assert the proper functioning of this method, there's no need for the tests for .generate_all to duplicate any assertion.
Testing the proper functioning of #generate in the tests for .generate_all leads to unnecessary dependencies between the tests.

How to write (and test) a Rails class that isn't a controller or a model

I'm writing a Rails app that will focus heavily on searching. For this purpose, I feel like I should split the actual search action on the controller off from another method to prepare the search parameters, just to make sure I can test accurately. However, I'm not sure where to put such a method. Others have told me to put it into a new class, but I'm not sure where you put an extra class file in a Rails project. Is there a customary directory location people usually store extra classes? If not, how would such a method work/be tested as a private method in the same controller by which it is called?
You would usually put extra classes in the lib folder. As for testing, nothing particular really. If you are using RSpec, simply do:
describe ExtraClass do
it 'saves the day'
end

Dependency on fixture data in rails initializer

I have an initializer which sets a default that is used throughout the app. The value is an ActiveRecord model, I'm essentially caching it for the lifetime of the app:
##default_region = Region.find_by_uri("whistler")
The record is guaranteed to be in the database: it's fixture data which is referenced by other models. This works fine, except in the test environment where the database is purged before every test run. (I'm running on edge rails and I think that's recent behavior: I used to be able to insert the data manually and keep it between test runs.) I also have the record in my regions.yml fixture file, but fixtures aren't loaded until after the rails initializer is done.
What's the right way to deal with such a dependency on fixture data? Or is there a better way to structure this? I'd rather not use a before_filter because there's no sense reloading this on each request: it will not change except on a different deployment.
I'd put something like this in region.rb:
def self.default_region
##default_region ||= Region.find_by_uri("whistler")
end
Then you can access it as Region.default_region wherever you need it, and it's only looked up once - the first time it's called - and by then the fixtures will be in place.
Not really familiar with Ruby or Rails... but why don't you try a "lazy-loading" scenario? Basically, have a global function that would check to see if the data was loaded, and if not, grab it from the database, then cache it. And if it was already cached, just return it.
That way, you won't be attempting to hit the database until that function is called for the first time and everything should be initialized by then.

Resources