Failure when expecting a belongs_to association to receive a message

14 views
Skip to first unread message

Mar Kim

unread,
Oct 11, 2019, 1:53:02 PM10/11/19
to rspec
I have a model `A` which `belongs_to` `B`. And I'm trying to write a unit test with `A` as `subject`:

```
expect(subject.b).to receive(:to_s)
```

Yet this spec always fails, saying that the instance of `B` did not receive the message, notwithstanding I have put `binding.pry` in the actual code to run and verified that `a.b.to_s` gets called.

```
class A
def validate_my_field
binding.pry # this fires
if b.to_s
super
end
end
```

I have even tried:

```
expect(a).to receive(:b).and_return(b)
expect(b).to receive(:to_s)
```

And:

```
expect_any_instance_of(b.class).to receive(:to_s)
```

Yet all expectations for `to_s` fail. Why could this be?

I'm in Rails 6 with Rspec 3.8.0.

Jon Rowe

unread,
Oct 11, 2019, 1:57:52 PM10/11/19
to rs...@googlegroups.com
Hi

In your example, unless you call:

```
a.validate_my_field
```

In the same test as:

```
# Note it doesn’t matter if this is `b` or `subject.b` if they are the same object.
expect(b).to receive(:to_s)
```

It won’t pass, as the method hasn’t been invoked in the scope.

e.g.

```
RSpec.describe “A” do
  subject(:a) { A.new b: b }
  let(:b) { B.new }

  describe “#validate_my_field” do
    it “will call to_s on B” do
      expect(b).to receive(:to_s)
      a.validate_my_field
    end
  end
end
```

Cheers
Jon Rowe
---------------------------

Mar Kim

unread,
Oct 11, 2019, 2:10:12 PM10/11/19
to rspec
Thanks, John. It sounds as if I represented my test as only holding 'expect' calls but then not actually running any of my app code. Apologies for the lack of clarity on my part:

The "describe" block that you illustrate, Including its invocation of the "validate_…" , including its invocation of the "validate_…" function, is in fact what I have. And when I run the spec, the "binding.pry" invocation does in fact fire, which confirms to me that my app code is running.

Jon Rowe

unread,
Oct 11, 2019, 2:12:00 PM10/11/19
to rs...@googlegroups.com
Hi Mar

Without providing a full example its hard to know where your issue lies then, it will be in the relationship between A and B, you will most likely be expecting on a different instance of B than is attached to your instance of A.

Jon Rowe
---------------------------

Mar Kim

unread,
Oct 11, 2019, 2:36:10 PM10/11/19
to rspec
Thanks, John. Sorry for the inconvenience. I was on my phone before.
Here's the class....
class A
  belongs_to :b

  validate :validate_my_field

  def validate_my_field
    binding.pry # this works
    if b.to_s
      super
    end
  end
end



And here's the spec
describe A do
  subject { create(:a) }

  describe "my check" do
    it "does something" do
      expect(subject.b).to receive(:to_s)
      subject.save
    end
  end
end

Can you see any issue?

Mar Kim

unread,
Oct 11, 2019, 2:43:20 PM10/11/19
to rspec
Well, John. I apologize for the unfruitful thread I've thrust upon you.

Now that I'm back at work after an hour's absence, I cannot reproduce the error.

Philipp Pirozhkov

unread,
Oct 12, 2019, 6:51:42 AM10/12/19
to rs...@googlegroups.com
Your example code expands to:

def subject #  subject { create(:a) }
  create(:a)
end

b = subject.b # this is where your `binding.pry` gets called, since it persists the record and does the validation
expect(b).to receive(:to_s) # this is too late to stub, as `to_s` was called already
subject.save # if I'm not mistaken, it's a no-op, since the object is not dirty

I'm struggling to understand the business meaning of `to_s` on `B`, and what's its implementation, specifically how it can return something falsey.

I'd suggest testing side effects, i.e.:

describe 'validations' do
  subject(:a) { A.new } # or `build_stubbed(:a)`

  it 'fails when an associated B record inspects as false' do
    allow(a.b).to receive(:to_s).and_return(nil)
    a.validate!
    expect(a.valid?).to be false
  end

  it 'passes when an associated B record inspects as true' do
    allow(a.b).to receive(:to_s).and_return(true)
    a.validate!
    expect(a.valid?).to be true
  end
end

That way, you check both branches of `validate_my_field` (assuming you don't have to cover `super`, and it fails the validation).

I also tend to think that persisting might be unnecessary to validate objects.

Please let me know what you think, but in any case, the more context you provide the better.

 
Reply all
Reply to author
Forward
0 new messages