Testing Observers via RSpec

528 views
Skip to first unread message

Craig Read

unread,
Jun 7, 2012, 10:49:08 PM6/7/12
to rails-...@googlegroups.com
Hi Everyone,

I have an observer which performs some calculations based on a number of relationships (including polymorphic relationships) and stores (aka caches) the results on a model related to the one being observed.

I ran into a bit of yak shaving while getting that observer adequately tested (via rspec).
I started off using stubs, but finally ran into an issue where a calculation was using a count of related rows using a "where" to filter the relations.
The test barfed because the array of stubs I was (quite happily) using for my other rspec tests wouldn't respond to where.

I tried for hours to fix this and got nowhere, so I ended up changing all my spec helpers so they instantiated real models instead of stubs.
The tests now pass, I refactored my test code to be a little cleaner, and the tests actually run a little faster (that bit has me stumped).
But I still feel dirty having those tests hit the database.

I have the rspec book, which covers testing models, controllers and views, but says nothing about testing observers.

How do you guys suggest testing observers via rspec?
Or should I test the impacts my observers have via cucumber?

Cheers,

--
Craig Read


Pat Allan

unread,
Jun 8, 2012, 5:18:10 AM6/8/12
to rails-...@googlegroups.com
Hi Craig

A link to a gist with the test code would certainly help here - but I would say you should be able to test observers by themselves easily enough, not needing any external dependencies.

One thing to note is that more and more, I'm considering ARel calls (chained or otherwise) from outside a model a code smell - and the same goes for find_by_*/find_all_by_* methods. Create scopes or class methods (that use ARel, etc under the hood) and then you've got clear single-method interfaces into the model for the records you need.

Cheers

--
Pat

> --
> You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
> To post to this group, send email to rails-...@googlegroups.com.
> To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.


Dave Perrett

unread,
Jun 8, 2012, 5:20:54 AM6/8/12
to rails-...@googlegroups.com
I usually do the heavy lifting in the model, and call model.do_heavy_lifting() in the observer, so they're lightweight and easy to stub. That may just be moving your problem around though.

Cheers

8 June 2012 2:49 PM

Malcolm Locke

unread,
Jun 8, 2012, 7:13:16 AM6/8/12
to rails-...@googlegroups.com
On Fri, Jun 08, 2012 at 12:49:08PM +1000, Craig Read wrote:
> How do you guys suggest testing observers via rspec?
> Or should I test the impacts my observers have via cucumber?

You have probably discovered this already, but I've found observers need
a little voodoo to test directly, something like this:

describe MymodelObserver do

# Can't use the default subject, need to set it up like this
subject { MymodelObserver.instance }

let(:mymodel) { mock_model(Mymodel) }

describe '#after_save' do

it "calls heavy_lifting() on the model" do
mymodel.should_receive(:heavy_lifting)
subject.after_save(mymodel)
end

end

end

This doesn't help you much with stubbing your where calls, but I plus
one Dave and Pat's advice on that.

Malc

Craig Read

unread,
Jun 8, 2012, 8:14:32 AM6/8/12
to rails-...@googlegroups.com
Thanks for the help guys. :)

I figured when it started feeling like "hard work" that I was definitely "doing it wrong". :o

Craig...

--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.

Nicholas Faiz

unread,
Jun 11, 2012, 8:49:24 AM6/11/12
to rails-...@googlegroups.com
Any reason why you couldn't place the calculations in a method on the model and call it on an after_save callback (you can hang some boolean logic on the callback too to ensure you only do the run if a condition is met)?

Cheers,
Nicholas

Craig Read

unread,
Jun 14, 2012, 6:13:19 PM6/14/12
to rails-...@googlegroups.com
Aleskey,

I think that's a fairly simplistic view of Rails.
Rails itself is made up of a lot more design patterns than Model, View and Controller.
And I reckon you'd be hard pressed to put together any half-decent rails site only using those patterns.

But as you implied, you need to use the right pattern for the right situation though.
A service may be more appropriate for what I'm doing, but I'm not sure what benefits it would give me over the Observer in this situation.
I'd still end up calling the service from callbacks in several different models whereas the one Observer can watch all those models and respond.

Nicholas,

The after_save callback is exactly what the Observer is doing.
However, in this instance, I want to update 2 models with the cached results of a calculation involving data from those and other models.
Rather than have the after_save in each model and increase coupling between them, I put it all in a single observer that can watch all those models.

I'll have my laptop with the source at RailsCamp if anybody is keen enough to take a look and provide feedback.

Cheers,

Craig.

On Mon, Jun 11, 2012 at 12:04 AM, Aleksey Gureiev <spyr...@gmail.com> wrote:
That's exactly what I do. Observers is a hack. Which part of MVC do they belong? Models? No. Controllers? No. And most definitely not views. I use observers in rare occasions when there's a cross-cut of some logic that doesn't belong to the business domain. For example, I would put some specific logging or reporting to GA there. Invalidating caches is another common choice.

If you end up using observers to set flags, send notifications, or God forgive, initializing objects, think again. Most probably, you are looking for Factory or a Service. I would create a app/services/create_user.rb and stick all logic of instantiating new user records, calling notifiers and doing everything else that belongs to the user creating workflow there. It becomes (a) reusable, and (b) testable.

- Aleksey


On Saturday, June 9, 2012 3:49:12 PM UTC+3, marsbomber wrote:
Testability is one of the reasons I try to keep myself away from observers or callbacks in general.

What's wrong with introducing plain ruby classes that wrapping up all those callback logics? Explicit over magics, no?

--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To view this discussion on the web visit https://groups.google.com/d/msg/rails-oceania/-/_5a48BpKJ-oJ.

To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.

Andrew Harvey

unread,
Jun 14, 2012, 6:26:00 PM6/14/12
to rails-...@googlegroups.com
Craig,

I'm by no means an expert but I'm working on some different testing strategies. I'd be interested to have a peek at railscamp. 

Andrew

Sent on the run. 

Gareth Townsend

unread,
Jun 14, 2012, 7:55:18 PM6/14/12
to rails-...@googlegroups.com
I don't see anything wrong with using an observer, but like a rake task, don't fill it with code. Use it as an entry point into a plain old ruby class that you can easily test.

* Test the outcome of triggering the observer in an integration/acceptance test.
* Unit test the plain old ruby class like you normally would.
Cheers,
Gareth Townsend





Reply all
Reply to author
Forward
0 new messages