简体   繁体   中英

rails unit testing with ActiveRecord associations without hitting the DB

TL;DR I'd like to know if I can test model methods that use querying (ie find , where ) without persisting test objects to the database.

So I'm new to rails, and working on an existing codebase.
One thing I've noticed is that our unit tests take forever to run.
Upon investigation, the culprit was, of course, that we persist everything to DB when we test our models.
So, I set out to try and write model tests that don't hit the DB, but I've come across a snag:
When a model has an association with other models, any operations that are performed on it assume that everything is persisted.

Let's take a look at an example-

class Parent < ActiveRecord::Base
  has_many :children, dependent: :destroy

  def default_child
    children.find_by(default: true)
  end

end

So naturally I want to test that my default_child method is working:

parent = Parent.new
default_child = parent.children.build(default: true)

assert_equal default_child, parent.default_child

but this test fails, because the actual result of parent.default_child is nil !
This is because internally, the default_child method uses find_by , which, it seems, only works in the context of persisted objects.

So I'm forced to write the test like so-

parent = Parent.new
# I don't want to do this!!
parent.save
# why 'create' and not 'build'?
default_child = parent.children.create(default: true)

assert_equal default_child, parent.default_child

Which is uglier and slower.
Is there any way I can test these operations in memory?

I've tried setting children manually ( parent.children = [ child1, child2 ] ).
This does not cause an error, but it seems that find_by doesn't look there, but rather in the DB...

I see that a similar question came up 3 years ago, without a conclusive answer, I'm wondering if anything's changed since then..

PS bonus question- what can I do about validations which are on: update ? seems like I have to persist the test object at least once before they are invoked..

Last question first. For validations, just assert that model is valid? :

parent = Parent.new
assert parent.valid?

As for he main question, there's no good answer for this, and people have philosophical differences on how things like these should be tested. My opinion is that you should be testing your business logic and avoid testing functionality provided to you by the framework (presumably already verified by the tests included with the framework itself).

I think you want to test the business logic behind Parent#default_child (btw, shouldn't it be default_children ?) that only returns associated Child objects with default = true . Assuming you trust ActiveRecord to work correctly, you can mock (you will need to add Mocha , or use something like RSpec ) find_by(default: true) to always return a Child object with default property to be set to true . Because you trust that ActiveRecord will do this for you. But I wouldn't mock every ActiveReocrd method. You will waste a lot of time and not be very happy in the end.

For some tests you may find that you absolutely have to have data. You can use fixtures available in Rails , or use something like FactoryGirl

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM