Testing speedbump: RSpec and ActiveRecord reload

As part of a pair project with Philly’s newest podcast host, Steven Rayesky, I helped to create a Rails application called Three Good Things. We only had a few days to propose an idea and build a prototype, but that made for a fun (and exhausting) few days of hacking.

Three Good Things Three Good Things

Now that the smoke has cleared, I’ve been looking for ways to improve the codebase every day. One weakness in our original protoype was the lack of tests (!), so I’ve been using this as an opportunity to learn and apply some solid testing principles.

Skipping tests originally seemed reasonable idea—we were under a tight deadline and were struggling with enough already—but that decision has haunted me since. Several times, I’ve refactored some code or fixed a bug only to break something else without realizing it. Lesson learned — I submit to the ways of testing.

My first testing speedbump

Following along with Everyday Rails Testing with RSpec, I started by writing tests for our models, their instance, and their class methods. Everything was going smoothly until I came to test a simple class method for the User model, .reset_all_streaks:

def self.reset_all_streaks
  User.all.find_each(&:reset_streak)
end

This method iterates through every user and calls #reset_streak on each one. The #reset_streak instance method checks to see whether a user has written at least three good things on the current day, and if not, it resets their current_streak attribute to 0.

In one of my tests for the class method reset_all_streaks, I wanted to confirm that if a user had a current_streak of 3, but then failed to write at least three things (here, they only write two things), then their current streak will be set to 0 after calling reset_all_streaks.

describe ".reset_all_streaks" do
  it "resets the current_streak of all users who have not completed today" do
    # create a valid user with a current_streak of 3
    user = User.create(email: "bob@example.com",
                    password: "valid_password",
                    first_name: "Bob",
                    location: "Philadelphia",
                    current_streak: 3)

    # the user writes two things                
    user.things.create(content: "This was a great thing that happened!")
    user.things.create(content: "Another great thing happened!")

    # call .reset_all_streaks
    User.reset_all_streaks

    # check that the user's streak is now set to 0
    expect(user.current_streak).to eq(0)
  end
end

The result:

Failures:

1) .reset_all_streaks resets the current_streak
    of all users who have not completed today

Failure/Error: expect(user.current_streak).to eq(0)
 
   expected: 0
        got: 3

It failed! I was expecting the user’s streak to be reset to 0, but it was still stuck at 3 after calling reset_all_streaks. More confusing, the tests for the instance method #reset_streak were working as expected, and reset_all_streaks seemed to be working if I littered it with puts statements. Somehow, the streak was getting reset as expected when reset_all_streaks was called during the test, but that change was getting reverted before I checked it with the Rspec matcher.

Reload to the rescue

After a lot of head-scratching and many, many puts later, the best thing happened: I found a StackOverflow post with a similar issue.

The problem: while the class method does update the record in the database table (explaining why puts suggested it was working), the instance variable user within the RSpec block doesn’t get updated. It needs to be reloaded after the class method does its thing for the changes to be reflected in the expect statement.

The solution was to call ActiveRecord’s reload on user before (or within) the expect, like so:

# check that the user's streak (after reloading user) is now set to 0
expect(user.reload.current_streak).to eq(0)

Now it works as expected, and I can carry on with testing.