For many years, as a Ruby developer, I have been using RSpec to write tests for Ruby applications. I often relied on the popular RSpec change matcher to verify if a specific value in an object had changed. However, recently I learned a new approach to its usage. But let’s start from the beginning.

Many people check if a value has changed using the following approach:

let(:user) { create(:user, first_name: "John") }

it "changes a user's first name" do
  user.update!(first_name: "Bob")

  expect(user.first_name).to eq("Bob")
end

To check whether a value has been modified in an object, we can utilize the RSpec change matcher.

let(:user) { create(:user, first_name: "John") }

it "changes a user's first name" do
  expect { user.update!(first_name: "Bob") }
    .to change { user.reload.first_name }
    .from("John")
    .to("Bob")
end

With this change, we can be certain that the object has changed its value from the initial state to the desired one.

But what if the object has multiple changed values? In such cases, I used to write separate tests to verify each value.

subject(:update_user) { user.update!(first_name: "Bob", last_name: "Doe") }

let(:user) { create(:user, first_name: "John", last_name: "Smith") }

it "changes a user's first name" do
  expect { update_user }
    .to change { user.reload.first_name }
    .from("John")
    .to("Bob")
end

it "changes a user's last name" do
  expect { update_user }
    .to change { user.reload.last_name }
    .from("Smith")
    .to("Doe")
end

One could argue that each test should focus on one thing. However, when we need to check multiple values within a single object (indicating a more complex service or action), we would end up writing a substantial amount of code to verify all those values.

Recently, I learned a clever trick that allows us to check multiple values within a single test by using Compound Expectations.

subject(:update_user) { user.update!(first_name: "Bob", last_name: "Doe") }

let(:user) { create(:user, first_name: "John", last_name: "Smith") }

it "changes a user's first name and last name" do
  expect { update_user }
    .to change { user.reload.first_name }.from("John").to("Bob")
    .and change { user.reload.last_name }.from("Smith").to("Doe")
end

As you can see, we are now able to verify multiple values for a single action (in this case, a simple data update using ActiveRecord).

There is no limitation to checking values in other objects within the same test if the service/action modifies multiple objects.

I hope you find this trick useful.