Michael MacDonald

Testing Scopes with Lambdas

In Rails, Testing on February 24, 2012 at 3:50 pm

How do you know that your scopes are correct? At work we had been plagued by mysterious multiple payments in one of our apps. It had been coded to prevent multiple payments yet some customers were somehow managing to do it. After a thorough investigation, I discovered that the problem simply came down to one of the scopes used in the code had been written incorrectly. Here it is:

scope :current, where('start_date <= ?', Date.today).where('end_date > ?', Date.today)

Do you see the problem? It is a scope that checks the object is current, that it has started but not yet ended. Unfortunately, this scope is missing a lambda.

scope :current, lambda { where('start_date <= ?', Date.today).where('end_date > ?', Date.today) }

Without the lambda, the scope is set to the date when the scope was first loaded so that Date.today remain static until the next deploy or when the production app is restarted. So it’ll work fine for a day and then it’ll start misbehaving. The lambda effectively defer the evaluation of the expression until it is needed. So each time you use Authorisation.current it’ll use the current date which is what you want.

So how do we avoid this mistake? Here’s the wrong way to test a scope that uses a lambda:

expired_auth = Authorisation.create(start_date: 4.days.ago, end_date: 2.days.ago)
current_auth = Authorisation.create(start_date: 2.days.ago, end_date: 2.days.from_now)

Authorisation.current.should include(current_auth)
Authorisation.current.should_not include(expired_auth)

This will pass the original non-lambda scope. It passes because it’s not really testing the dynamic nature of the scope provided by the lambda. For this I use the Timecop gem:

auth = Authorisation.create(start_date: 2.days.ago, end_date: 2.days.from_now) # or use a factory to give you a current auth
Authorisation.current.should include(auth)

Timecop.freeze(2.days) # fast forward by 2 days so that the current auth is now by definition no longer current
Authorisation.current.should_not include(auth)

Now this test fails with the non-lambda scope. When I change the scope to use lambda, the test now passes.


Test Business Behaviour, Don’t Rely on UI

In Testing on January 26, 2011 at 1:15 am

I often see Cucumber features written like this:

Given I a have an account with email: "user@test.com" and password: "password"
When I go to the log in page
And I fill in "Email" with "user@test.com"
And I fill in "Password" with "password"
And I press "Log in"
Then I should see "Logged in successfully."
And I should not see "Log in"

It works and does the job but I feel that it isn’t enough. It’s fragile in several ways.

Faster Cucumbering With Pagination

In Rails, Testing on November 25, 2010 at 5:30 pm

When writing a cucumber feature that involves pagination, the easiest thing to do is to create the required number of objects to generate pagination.

Given 31 tasks exist  # pagination defaults to 30

The downside of course is speed. Creating a large number of complex objects can add a lot of extra wait time to your cucumber runs. In my case, although I only had a few scenarios that involved pagination, my per_page setting was 50 and the objects I was creating were complex. The end result was a extra couple of (unnecessary) minutes. It wasn’t too bad so I left myself a TODO to fix it one day and that day finally came.