Let's expect change and reload!
Tags: ruby, rails, testing, webdev
Published: 2023-04-03 17:44:09

It's often that we create a service that is supposed to change an attribute on an ActiveRecord object. Testing such a service is tricky at times.

## Expect change

Let's say we have an incorrect service that is supposed to update 
a name of a user in a database, which is looking like this:

```ruby
class UpdateUserName
  def initialize(user)
    @user = user
  end

  def call
    # TODO: Will be done in close future, until 2200
  end

  private

  attr_reader :user
end
```

And we have a spec that is supposed to test it:
```ruby
require 'rails_helper'

RSpec.describe UpdateUserName do
  it "should update user name" do
    user = create(:user)

    described_class.new(user).call

    expect(user.name).to eq "funny name"
  end
end
```

What happens when we run the spec?

![Passing spec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dutxx86iifg5r5j73fmz.png)


![Wtf](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/txzilqvtic8tolkarjbe.png)

Why has it passed you shall ask. The name of the user was always "funny name" although the call of service did not change anything, the spec passed.

We shall rewrite it to something like this:
```ruby
require 'rails_helper'

RSpec.describe UpdateUserName do
  it "should update user name" do
    user = create(:user)

    expect { described_class.new(user).call }.to change(user, :name).to "funny name"
  end
end
```

![Failing spec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67zyhakrr9ldeutdjfbv.png)

Awesome! We've discovered an incorrect implementation with a spec!

## Reload an object

Going forward with the same example of spec:
```ruby
require 'rails_helper'

RSpec.describe UpdateUserName do
  it "should update user name" do
    user = create(:user, name: "not so funny name")

    expect { described_class.new(user).call }.to change(user, :name).to "funny name"
  end
end
```
but we have upgraded our service and it is looking like this:
```ruby
class UpdateUserName
  def initialize(user)
    @user = user
  end

  def call
    user.name = "funny name"
  end

  private

  attr_reader :user
end
```

What happens if I run the test?


![Passing spec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q44v13czwdxil4l26r5s.png)

Is the service implemented correctly? In this particular case, we expect it to persist changes to database, so it is not correct, but spec is passing.

Let me fix the spec:
```ruby
require 'rails_helper'

RSpec.describe UpdateUserName do
  it "should update user name" do
    user = create(:user, name: "not so funny name")

    expect { described_class.new(user).call }.to change { user.reload.name }.to "funny name"
  end
end
```

![Failing spec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rv1zhji417vs1nx8igvh.png)

So now we've discovered incorrect implementation again! Let me fix the service:

```ruby
class UpdateUserName
  def initialize(user)
    @user = user
  end

  def call
    user.name = "funny name"
    user.save
  end

  private

  attr_reader :user
end
```

![Passing specs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l14z4ic1vl5tuwa3hzk0.png)

![Victory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pdmzbk52xuf7n74o0mru.png)

## Conclusion

Usually, when we want to test services, we should expect a change of an attribute and reload it to see if the change really happened or maybe it is only on the object passed.