Customer matcher diff and failure message help

29 views
Skip to first unread message

Arup Rakshit

unread,
May 13, 2019, 4:56:24 AM5/13/19
to rspec
Hi,

I am trying to write a custom matcher, but not able to show the diff and error message correctly. I need help with it.

RSpec::Matchers.define :have_constant do |const|
  match do |owner|
    owner.const_defined?(const) && owner.const_get(const) == @second
  end

  chain :with_value do |second|
    @second = second
  end

  diffable

  failure_message do |actual|
    "expected that #{const} would be defined in #{actual} with #{@second}"
  end
end

And the spec is:

describe "FeatureService" do
  describe 'constants' do
    subject { FeatureService }

    it { is_expected.to have_constant(:IMO_NUMBER_VALIDATION).with_value('6cjXBWdq') }
    it { is_expected.to have_constant(:JOB_TYPES).with_value('6cjXBWdq') }
    # ...
  end
end

The actual class is:

class FeatureService
  JOB_TYPES = 'WN2HsoIm'.freeze

  #...
end

When I run the test the failure message is not quiet helpful. So how can I tweak the custom matcher so that it can be helpful when it writes to stdout than what I see currently.

$ rspec ./spec/services/feature_service_spec.rb:8
Run options: include {:locations=>{"./spec/services/feature_service_spec.rb"=>[8]}}
F

Failures:

  1) FeatureService constants should have constant :JOB_TYPES with value "6cjXBWdq"
     Failure/Error: it { is_expected.to have_constant(:JOB_TYPES).with_value('6cjXBWdq') }
     
       expected that JOB_TYPES would be defined in FeatureService with 6cjXBWdq
       Diff:
       @@ -1,2 +1,2 @@
       -:JOB_TYPES
       +FeatureService
       
     # ./spec/services/feature_service_spec.rb:8:in `block (3 levels) in <top (required)>'

Jon Rowe

unread,
May 13, 2019, 5:09:27 AM5/13/19
to rs...@googlegroups.com
Hi Arup

You need to define `expected` and `actual` to surface the variables you wish to diff. In your case `expected` should be `@second` and `actual` should be `owner.const_defined?(const) && owner.const_get(const)`.

See the matcher protocol documentation for more information.


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

Arup Rakshit

unread,
May 14, 2019, 3:29:20 AM5/14/19
to rspec

Hello Jon,

Thanks for your reply.

I modified it as:

RSpec::Matchers.define :have_constant do |const|
    match do |owner|
      @actual = owner.const_defined?(const) && owner.const_get(const)
      @expected = @second
      owner.const_defined?(const) && owner.const_get(const) == @second
    end

    chain :with_value do |second|
      @second = second
    end

    diffable

    failure_message do |actual|
      "expected that #{const} would be defined with #{actual} but got #{@second}"
    end
  end

Now the error message is close, but not quiet correct.

Failures:

  1) FeatureService constants should have constant :TABLE_OF_PREVIOUS_INSPECTIONS with value "3oelqiFW1"
     Failure/Error: it { is_expected.to have_constant(:TABLE_OF_PREVIOUS_INSPECTIONS).with_value('3oelqiFW1') }
     
       expected that TABLE_OF_PREVIOUS_INSPECTIONS would be defined with 3oelqiFW but got 3oelqiFW1
       Diff:
       @@ -1,2 +1,2 @@
       -:TABLE_OF_PREVIOUS_INSPECTIONS
       +"3oelqiFW"

I want it to be like:

Failures:

  1) FeatureService constants should have constant :TABLE_OF_PREVIOUS_INSPECTIONS with value "3oelqiFW1"
     Failure/Error: it { is_expected.to have_constant(:TABLE_OF_PREVIOUS_INSPECTIONS).with_value('3oelqiFW1') }
     
       expected that TABLE_OF_PREVIOUS_INSPECTIONS would be defined with 3oelqiFW but got 3oelqiFW1
       Diff:
       @@ -1,2 +1,2 @@
       -"3oelqiFW1"
       +"3oelqiFW"

What needs to be tweaked ?

Jon Rowe

unread,
May 14, 2019, 10:02:57 AM5/14/19
to rs...@googlegroups.com
I believe you still need to defined expected, also I think you have `actual` and `@second` the wrong way around in your description.

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

Arup Rakshit

unread,
May 14, 2019, 10:57:17 AM5/14/19
to rspec
Hm, I am confused too. Which part i need to change? Not sure from where the symbol is coming though.

Jon Rowe

unread,
May 14, 2019, 11:16:39 AM5/14/19
to rs...@googlegroups.com
As I said, you need to define the methods, e.g not the instance variables, they are considered “private” and are not called what you expect in this case.

This does what you want:

RSpec::Matchers.define :have_constant do |const|
  match do |owner|
    @actual_value = owner.const_defined?(const) && owner.const_get(const)
    actual == expected
  end

  def actual
    @actual_value
  end

  def expected
    raise ArgumentError, "expected value not set" unless @expected_value
    @expected_value
  end

  chain :with_value do |second|
    @expected_value = second
  end

  diffable

  failure_message do
    "expected that #{const} would be defined with #{expected} but got #{actual}"
  end
end


Cheers
Jon Rowe
---------------------------
On 13 May 2019 at 10:56, Arup Rakshit wrote:

Arup Rakshit

unread,
May 16, 2019, 2:26:09 AM5/16/19
to rs...@googlegroups.com
I just added your code and run the test intentionally to see the failure message. But there is no diff printed.

rspec ./spec/services/feature_service_spec.rb
.....F.

Failures:

  1) FeatureService constants should have constant "3oelqiFW1" with value "3oelqiFW1"
     Failure/Error: it { is_expected.to have_constant(:TABLE_OF_PREVIOUS_INSPECTIONS).with_value('3oelqiFW1') }
       expected that TABLE_OF_PREVIOUS_INSPECTIONS would be defined with 3oelqiFW1 but got 3oelqiFW
     # ./spec/services/feature_service_spec.rb:12:in `block (3 levels) in <top (required)>'
     # ./spec/rails_helper.rb:41:in `block (3 levels) in <top (required)>'
     # /Users/aruprakshit/.rvm/gems/ruby-2.3.3@DockingTestApp/gems/database_cleaner-1.6.1/lib/database_cleaner/generic/base.rb:16:in `cleaning'
     # /Users/aruprakshit/.rvm/gems/ruby-2.3.3@DockingTestApp/gems/database_cleaner-1.6.1/lib/database_cleaner/base.rb:98:in `cleaning'
     # /Users/aruprakshit/.rvm/gems/ruby-2.3.3@DockingTestApp/gems/database_cleaner-1.6.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
     # /Users/aruprakshit/.rvm/gems/ruby-2.3.3@DockingTestApp/gems/database_cleaner-1.6.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
     # ./spec/rails_helper.rb:40:in `block (2 levels) in <top (required)>'

Finished in 0.47436 seconds (files took 10.93 seconds to load)
7 examples, 1 failure


--
You received this message because you are subscribed to the Google Groups "rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rspec+un...@googlegroups.com.
To post to this group, send email to rs...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/dejalu-217-545311dc-8609-4a6b-82f8-2a32a1e53f37%40jonrowe.co.uk.
For more options, visit https://groups.google.com/d/optout.

Jon Rowe

unread,
May 17, 2019, 6:42:46 AM5/17/19
to rs...@googlegroups.com
Hi Arup

Yes thats correct, we don’t diff single line strings as there changes are covered in the overall failure.

Cheers
Jon Rowe
---------------------------
Reply all
Reply to author
Forward
0 new messages