Using implicit `subject` with `expect` in RSpec-2.11 - ruby

With the new expect syntax in rspec-2.11, how is it possible to use the implicit subject? Is there a better way than explicitly referencing subject, like below?
describe User do
it 'is valid' do
expect(subject).to be_valid # <<< can `subject` be implicit?
end
end

If you configure RSpec to disable the should syntax, you can still use the old one-liner syntax, since that doesn't involve should being added to every object:
describe User do
it { should be_valid }
end
We briefly discussed an alternate one-liner syntax, but decided against it since it wasn't needed and we felt like it might add confusion. You can, however, easily add this yourself if you prefer how it reads:
RSpec.configure do |c|
c.alias_example_to :expect_it
end
RSpec::Core::MemoizedHelpers.module_eval do
alias to should
alias to_not should_not
end
With this in place, you could write this as:
describe User do
expect_it { to be_valid }
end

With Rspec 3.0 you can use is_expected as described here.
describe Array do
describe "when first created" do
# Rather than:
# it "should be empty" do
# subject.should be_empty
# end
it { should be_empty }
# or
it { is_expected.to be_empty }
end
end

One could use the new named subject syntax, although it's not implicit.
describe User do
subject(:author) { User.new }
it 'is valid' do
expect(author).to be_valid
end
end

Related

Testing with Rspec - The correct way

My weakest point when it comes to coding, is using TDD & BDD methods - I tend to just write code.. but it is something that I am trying to work on.
Could anyone point out the best way to go about the following problem:
Class1:
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
if react_upon? xml.something
puts 'yeah'
else
puts 'nah'
end
end
def react_upon?(xml_code)
#code here
end
end
end
So lets say I wanted to test this class, or build it from a TDD point of view so I write my tests:
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it 'should check that it is valid' do
myclass.process '<some><xml>here</xml></some>'
end
it 'should output yea'
end
end
How do I test that it is calling the react_upon? method. Do I even want to see it is calling it?
Is the proper way to test it, to test all the functions like the react_upon? itself independently of the other functions?
This is properly the main thing that is most confusing me with this sort of testing. Am I testing the whole class, or just individually testing the functions, and not their interactions with the other functions in that class?
Also I realize the the react_upon? might not adhere to the Single responsibility principle and I would probably move that out to its own module/class which I could test using a stub.
If anyone can shed some light on this for me that would be awesome.
edit:
describe TempMod::MyClass do
let (:valid_planning_status_xml) {
'<StatusUpdate> <TitleId>2329</TitleId> <FromStatus>Proposed</FromStatus> <ToStatus>Confirmed</ToStatus> </StatusUpdate>'
}
let(:config) { double }
let(:status_resolver) { double }
subject(:message_processor) { TempMod::MyClass.new config, status_resolver }
context 'Given that the message XML is valid' do
it 'should check the context of the message' do
expect(message_processor.process valid_planning_status_xml).to call :check_me
end
context 'Given that the message is for a planning event update' do
it 'should call something' do
pending
end
end
context 'Given that the message is for a recording job update' do
end
context 'Given that the message is for a video title update' do
end
end
end
Your question confused me a bit is this what you are asking
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
react_upon?(xml.something) ? 'yeah' : 'nah'
end
def react_upon?(xml_code)
#code here
end
end
end
Then test like
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it "should respond to react_upon?" do
expect(myclass).to respond_to(:react_upon?)
end
it "should react_upon? valid xml" do
expect(myclass.react_upon?(YOUR VALID REACTION GOES HERE)).to be_true
end
it "should not react_upon? invalid xml" do
expect(myclass.react_upon?(YOUR INVALID REACTION GOES HERE)).to be_false
end
it "should say 'yeah' if it is valid" do
expect(myclass.process('<some><xml>here</xml></some>')).to eq('yeah')
end
it "should say 'nah' if it is invalid" do
expect(myclass.process('<some><xml>here</some>')).to eq('nah')
end
it 'should check the context of the message' do
expect(myclass).to receive(:react_upon?).with('<some><xml>here</xml></some>')
myclass.process('<some><xml>here</xml></some>')
end
end
end
Right now your tests have no expectations so I added one that expects myclass to respiond_to the react_upon? method and another that expects myclass.process(xml) to respond with a String that equals yeah.

Simplifying rspec unit tests

A lot of times in unit test in rspec having to specify both a context and a let is somewhat cumbersome and seems unnecessary. For example:
context 'type = :invalid' do
let(:type) { :invalid }
it { expect { subject }.to raise_error(ArgumentError) }
end
It would be nicer (in aggregate over lots of tests) if I could do something like:
let_context type: :invalid do
it { expect { subject }.to raise_error(ArgumentError) }
end
The method would define a context and let(s) for me and the context's argument would be something like type = :invalid or let(:type) { :invalid } because I don't have anything else to say other that the fact that this variable has changed.
A lot of times in unit test in rspec having to specify both a context and a let is somewhat cumbersome
Sounds like you might want to use a RSpec shared context.
UPDATE
RSpec provides a DSL for the syntax you're suggesting: a shared example. For example:
RSpec.shared_examples "some thang" do |type|
it { expect { subject }.to raise_error(ArgumentError) }
end
RSpec.shared_examples "a thang" do
include_examples "some thang", :invalid
# Or whatever is more appropriate for your domain
# I.e., If you're testing subclass behavior use it_should_behave_like()
end
actually you could get less lines by following some http://betterspecs.org recommendations:
context 'type = :invalid' do
let(:type) { :invalid }
it { expect{ subject }.to raise_error(ArgumentError)
end
Your variant could be read as
it raises an error expect subject to raise error
While this is much cleaner
it expect subject to raise_error
Nevertheless it is pretty off topic :)
UPD
Oh. Really you can't pass two blocks to method, so below example is not valid Ruby :)
context(:type) { :invalid } do
it{ expect{ subject }.to raise_error(ArgumentError)
end
While your example
let_context type: :invalid do
...
end
Won't do lazy execution, like let does

RSpec 'specify' passes with explicit subject, but not with implicit subject

I thought I understood how implicit subjects work in RSpec, but I don't.
Why is it that in the following example, the first spec with an explicit subject passes, but the second spec using an implicit subject fails with "undefined method `matches' for #":
class Example
def matches(str) ; true ; end
end
describe Example do
subject { Example.new }
specify { subject.matches('bar').should be_true }
it { matches('bar').should be_true }
end
(I'm using rspec 1.3, but I verified the same behavior with 2.10.1.)
Step back to some basic ruby: You're basically calling self.matches, and self in this case is an RSpec example.
You can call things like "should" on this example, with parameters, so you might try something like:
it { should matches('bar') }
but this will fail; there's no method matches on self still!
In this case, though, the subject really is the matches method, not the Example instance. So, if you want to continue using the implicit subject, your tests might be something like:
class Example
def matches(str) ; str == "bar" ; end
end
describe Example do
describe "#matches" do
let(:method) { Example.new.method(:matches) }
context "when passed a valid value" do
subject { method.call("bar") }
it { should be_true }
end
context "when passed an invalid value" do
subject { method.call("foo") }
it { should be_false }
end
end
end
I don't think you can call any methods of implicit subject. Implicit subject meanings you don't need to specify the subject, but if you want call any method you need to specify the subject.
Although Chris provided very nice answer, I recommend you to take a look at this blog post: http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/

Minitest spec custom matcher

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.
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
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.

Rspec let scoping

I believe I have a problem with rspec let and scoping. I can use the methods defined with let in examples (the "it" blocks), but not outside (the describe block where I did the let).
5 describe Connection do
8 let(:connection) { described_class.new(connection_settings) }
9
10 it_behaves_like "any connection", connection
24 end
When I try to run this spec, I get the error:
connection_spec.rb:10: undefined local
variable or method `connection' for
Class:0xae8e5b8 (NameError)
How can I pass the connection parameter to the it_behaves_like?
let() is supposed to be scoped to the example blocks and unusable elsewhere. You don't actually use let() as parameters. The reason it does not work with it_behaves_like as a parameter has to do with how let() gets defined. Each example group in Rspec defines a custom class. let() defines an instance method in that class. However, when you call it_behaves_like in that custom class, it is calling at the class level rather than from within an instance.
I've used let() like this:
shared_examples_for 'any connection' do
it 'should have valid connection' do
connection.valid?
end
end
describe Connection do
let(:connection) { Connection.new(settings) }
let(:settings) { { :blah => :foo } }
it_behaves_like 'any connection'
end
I've done something similar to bcobb's answer, though I rarely use shared_examples:
module SpecHelpers
module Connection
extend ActiveSupport::Concern
included do
let(:connection) { raise "You must override 'connection'" }
end
module ClassMethods
def expects_valid_connection
it "should be a valid connection" do
connection.should be_valid
end
end
end
end
end
describe Connection do
include SpecHelpers::Connection
let(:connection) { Connection.new }
expects_valid_connection
end
The definition of those shared examples are more verbose than using shared examples. I guess I find "it_behave_like" being more awkward than extending Rspec directly.
Obviously, you can add arguments to .expects_valid_connections
I wrote this to help a friend's rspec class: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html ...
Redacted -- completely whiffed on my first solution. Ho-Sheng Hsiao gave a great explanation as to why.
You can give it_behaves_like a block like so:
describe Connection do
it_behaves_like "any connection" do
let(:connection) { described_class.new(connection_settings) }
end
end
I've discovered that if you do not explicitly pass the parameter declared by let, it will be available in the shared example.
So:
describe Connection do
let(:connection) { described_class.new(connection_settings) }
it_behaves_like "any connection"
end
connection will be available in the shared example specs
I found what works for me:
describe Connection do
it_behaves_like "any connection", new.connection
# new.connection: because we're in the class context
# and let creates method in the instance context,
# instantiate a instance of whatever we're in
end
This works for me:
describe "numbers" do
shared_examples "a number" do |a_number|
let(:another_number) {
10
}
it "can be added to" do
(a_number + another_number).should be > a_number
end
it "can be subtracted from" do
(a_number - another_number).should be < a_number
end
end
describe "77" do
it_should_behave_like "a number", 77
end
describe "2" do
it_should_behave_like "a number", 2
end
end

Resources