Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Minitest spec custom matcher

Tags:

ruby

minitest

I have a line in my test:

page.has_reply?("my reply").must_equal true

and to make it more readable I want to use a custom matcher:

page.must_have_reply "my reply"

Based on the docs for https://github.com/zenspider/minitest-matchers I expect I need to write a matcher which looks something like this:

def have_reply(text)
  subject.has_css?('.comment_body', :text => text)
end
MiniTest::Unit::TestCase.register_matcher :have_reply, :have_reply

The problem is that I can't see how to get a reference to the subject (i.e. the page object). The docs say "Note subject must be the first argument in assertion" but that doesn't really help.

like image 893
Andy Waite Avatar asked Sep 04 '12 16:09

Andy Waite


2 Answers

There is a little example, you can create a class which should responds to set of methods matches?, failure_message_for_should, failure_message_for_should_not. In matches? method you can get the reference to the subject.

class MyMatcher
  def initialize(text)
    @text = text
  end

  def matches? subject
    subject =~ /^#{@text}.*/
  end

  def failure_message_for_should
    "expected to start with #{@text}"
  end

  def failure_message_for_should_not
    "expected not to start with #{@text}"
  end
end

def start_with(text)
  MyMatcher.new(text)
end
MiniTest::Unit::TestCase.register_matcher :start_with, :start_with

describe 'something' do
  it 'must start with...' do
    page = 'my reply'
    page.must_start_with 'my reply'
    page.must_start_with 'my '
  end
end
like image 113
hs- Avatar answered Nov 11 '22 04:11

hs-


There are many ways to get what you want here. The easiest way is to not mess with assertions, expectations, or matchers at all and just use an assert. So, assuming you already have the has_reply? method defined, you could just use this:

assert page.has_reply?("my reply")

But, that doesn't get you the must_have_reply syntax you are asking for. And I doubt you really have a has_reply? method. So, let's start.

Your asked "how to get a reference to the subject (i.e. the page object)". In this case the subject is the object that the must_have_reply method is defined on. So, you should use this instead of subject. But its not as straightforward as all that. Matchers add a level of indirection that we don't have with the usual Assertions (assert_equal, refute_equal) or Expectations (must_be_equal, wont_be_equal). If you want to write a Matcher you need to implement the Matcher API.

Fortunately for you you don't really have to implement the API. Since it seems you are already intending on relying on Cabybara's have_css matcher, we can simply use Capybara's HaveSelector class and let it implement the proper API. We just need to create our own Matchers module with a method that returns a HaveSelector object.

# Require Minitest Matchers to make this all work
require "minitest/matchers"
# Require Capybara's matchers so you can use them
require "capybara/rspec/matchers"

# Create your own matchers module
module YourApp
  module Matchers
    def have_reply text
      # Return a properly configured HaveSelector instance
      Capybara::RSpecMatchers::HaveSelector.new(:css, ".comment_body", :text => text)
    end

    # Register module using minitest-matcher syntax
    def self.included base
      instance_methods.each do |name|
        base.register_matcher name, name
      end
    end
  end
end

Then, in your minitest_helper.rb file, you can include your Matchers module so you can use it. (This code will include the matcher in all tests.)

class MiniTest::Rails::ActiveSupport::TestCase
  # Include your module in the test case
  include YourApp::Matchers
end

Minitest Matchers does all the hard lifting. You can now you can use your matcher as an assertion:

def test_using_an_assertion
  visit root_path
  assert_have_reply page, "my reply"
end

Or, you can use your matcher as an expectation:

it "is an expectation" do
  visit root_path
  page.must_have_reply "my reply"
end

And finally you can use it with a subject:

describe "with a subject" do
  before  { visit root_path }
  subject { page }

  it { must have_reply("my reply") }
  must { have_reply "my reply" }
end

Important: For this to work, you must be using 'gem minitest-matchers', '>= 1.2.0' because register_matcher is not defined in earlier versions of that gem.

like image 1
blowmage Avatar answered Nov 11 '22 04:11

blowmage