[rspec-users] Smart mocks for ActiveRecord to speed up tests
matt at mattwynne.net
Mon Apr 16 09:00:11 UTC 2012
On 16 Apr 2012, at 03:42, David Chelimsky wrote:
> On Sunday, April 15, 2012 at 9:23 PM, Rodrigo Rosenfeld Rosas wrote:
>> Em 14-04-2012 10:11, David Chelimsky escreveu:
>>> On Friday, April 13, 2012 at 2:09 PM, Rodrigo Rosenfeld Rosas wrote:
>>>> Hello old friends, I'm getting back to Rails and Ruby programming full time again (Yay!)
>>>> I've stopped doing Rails programming to work with Grails in 2009 after 2 years working with Rails due to a better job offer.
>>>> Since then I've changed my job once more, but still on Grails, but I found a critical bug in Grails more than a week ago that didn't seem to get much attention and is affecting the production system I maintain:
>>>> As I couldn't work around that bug and I can't see myself reading the Grails source code (a real mess!) I decided to take another approach that would allow me to get back to Rails programming.
>>>> I created a Devise / Warden strategy to send the cookies to the current system and it will return me the current logged in user. So I can set up Nginx to proxy some requests affected by the Grails bug to this new Rails application.
>>>> I've finished this action last Monday but when I was about to send all the hard work to the server lots of my directories were suddenly gone when I was using them. I still don't know what happened but I've replaced my hard-drive just in case, but it means I had to do all over again :(
>>>> Anyway, now I got it back to work but testing a single action (an update/insert one) will take about a full second to run using RSpec and FactoryGirl.
>>>> I really don't like Grails but they had a great feature for unit testing. They were able to mock their domain class (that is how they call their models) in such a way that they can perform most of the operations you can do on real objects.
>>>> Of course it have another serious bug that will prevent me to use it in lots of cases:
>>>> But the idea is great as it works seamlessly like in a memory database.
>>>> So, I'd like to be able to keep my tests easy to read but avoid touching the database that much.
>>>> But my controller spec rely on lots of the setting of ActiveRecord for my models, like maximum calculations in before callbacks, custom foreign keys and depends: :delete_all in some has_many associations. Also I do also need a user object that Devise will accept on its sign_in helper method.
>>>> Is there any place I could read more about writing faster tests without getting crazy writing so many mocks manually. For example, mocking a Grails domain class in a unit test would be as easy as:
>>>> class UserTests...
>>>> Then I'm able to do most operations using the Grails domain class API using the User mock.
>>>> Is there something like this for Rails and Rspec? Is there some way that I could set up FactoryGirl to use those mocks instead of hitting the database when I want to?
>>>> Am I going in the wrong direction here? If so, what would be the right direction?
>>>> Thanks in advance.
>>>> Glad to be part of the Ruby, Rails and Rspec community full-time again :D
>>> You can use mock_model or stub_model to create model instances, or you can use FactoryGirl.build instead of FactoryGirl.create. Only mock_model will actually avoid db calls entirely (the others will still query for column names even if you don't save anything).
>>> Those techniques, however, don't help you with finders. You still have to stub any finders with the object generated by any of the aforementioned techniques:
>>> thing = mock_model(Thing)
>>> Thing.stub(:domain_specific_finder => [thing])
>> I'm not sure what you're suggesting. For example, the first part of my controller action looks like this:
>> instance = Model.where(id: params[:id]).first_or_initialize(parent_id: params[:parentId])
> The controller is interacting with an ActiveRecord API on the model instead of a domain-specific-finder. I'd recommend wrapping this in a method on the model and then mocking _that_ method.
>> The spec starts with something like this:
>> root = FactoryGirl.create :field, label: 'ROOT'
>> post 'action', parentId: root.id
>> In the before/after Model callbacks there will be other kind of finders for the same class.
> Think I'd need to see more of the code to comment on this.
>> How would you suggest me to change this block to use FactoryGirl.build and mock Model?
> Use one or the other (they both serve a similar purpose).
>> Also, I couldn't find how to mock 'open' from 'open-uri', so I'm currently mocking my controller's action:
>> controller.should_receive(:clear_fields_cache).exactly(3).times # 1 creation and 2 updates
>> The action is implemented this way:
>> require 'open-uri'
>> def clear_fields_cache
>> open INTEGRATION_BASE_URL + 'clearModelCache',
>> 'Cookie' => authentication_cookie_string # includes JSESSIONID and rememberMe
>> But maybe it would be a great idea to check for the specific URL and cookie handling and I could do so if I was able to mock the 'open' method from 'open-uri'. Any ideas on how to do that?
> I use https://github.com/chrisk/fakeweb for this sort of thing. It operates more like a stub than a message expectation.
>>> It's not pretty, but it's the best way I know of to get what you're after. I started a lib called stubble a few years back that attempted to address this more holistically (https://github.com/dchelimsky/stubble) but I never released it as a gem because there were just too many different ways to access model data via the ActiveRecord API and they don't all flow through a single part of the codebase that we can override in an extension.
>> I agree with you. I've taken a look at the library but as you can see I'm using the new 'where' API and this probably will keep changing each new AR release...
> This is exactly why I like to wrap finders in domain-specific methods on the model :)
>> Also, in Grails case, the mocked domain classes are maintained alongside with the real classes, so there is little chance of them getting out of sync. But of course the mocks have some limitation, like not being able to deal with custom SQL fragments:
>> Other than that there are also some bugs when dealing with association ids.
>> I never thought it would be easy to have something like this for ActiveRecord but once there is a long time since I last often worked with Rails, things might have changed ;)
>> Thank you a lot for your input and glad to be talking to you again :)
>> rspec-users mailing list
>> rspec-users at rubyforge.org
> rspec-users mailing list
> rspec-users at rubyforge.org
What David said.
There's a great rule of thumb from Steve Freeman and Nat Pryce, who invented mocking. They say:
"Don't mock types you don't own"
If your Thing inherits from ActiveRecord, you don't own it. That's why it's hard to mock, because the ActiveRecord API is very generic and has nothing to do with your domain. That's also why it's a bad idea to add anything more than skeletal behaviour to it. Instead, I'd suggest you add behaviour to a PORO adapter or wrapper around the ActiveRecord model that makes sense in your domain, and can therefore be easily mocked.
Freelance programmer & coach
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the rspec-users