How to define Rspec custom matcher for its(:field) - ruby-on-rails

I want to declare custom matcher for Rspec 2
I am using rspec 2.13 and rails 3.2.13.
I have tried to write something like this:
RSpec::Matchers.define :be_present do |expected|
match do
expected !be_empty
end
end
but when I use this in spec, it doesn't works
Failures:
1) NewsletterMailer.send_newsletter_to_groups from
Failure/Error: its(:from) { should be_present }
ArgumentError:
wrong number of arguments (1 for 0)
Spec code:
describe NewsletterMailer do
describe '.send_newsletter_to_emails' do
let(:user) { create(:admin) }
let(:user2) { create(:user) }
subject { NewsletterMailer.send_newsletter_to_emails(newsletter.id, "#{user.email}, #{user2.email}") }
its(:to) { should == [user.email, user2.email] }
its(:from) { should be_present }
its(:subject) { should be }
end
Edit:
I want to have reverse of logic like this:
its(:from) { should_not be_nil }

I'm not sure why you need a custom matcher at all here. Wouldn't
its(:from) { should be }
work for you?
See here:
https://www.relishapp.com/rspec/rspec-expectations/v/2-3/docs/built-in-matchers/be-matchers#be-matcher
obj.should be # passes if obj is not nil
Update:
Since apparently the question is how to write a custom matcher for an existing predicate present?, then the answer is: rspec already provides that, and there is still no need to write a custom matcher.
https://www.relishapp.com/rspec/rspec-expectations/v/2-3/docs/built-in-matchers/predicate-matchers
For any predicate #foo? on an object, you can just write should be_foo. Rspec will even define matchers for predicates that start with "has" like has_foo? with a more natural syntax, so that you can just write should have_foo.

Solution:
RSpec::Matchers.define :be_present do |expected|
match do |actual|
actual && actual.present?
end
end
Looks like this helper already exists in Rspec.
I just reinvented wheel. But I will still let this answer and don't delete this post.
It will be tip for developers, how to declare custom matcher without parameter eg.
be_something_special

Related

What a test should test with Rspec in Ruby on Rails application

I'm beginner in Rspec, and actually we asked me to do Rspec test for somes method which are already build but which never have been test (they don't write test before building the method).
And now I'm struggling to know how can I test my method, here is example:
class ConnectorJob < ActiveJob::Base
queue_as :connector
def perform(tenant_id = nil, debug = false)
tenant_to_sync = Tenant.to_sync(tenant_id)
return if tenant_to_sync.empty?
tenant_to_sync.each do |tenant|
service = MyAPP::ContactToSync.new(tenant, debug).call
if service.success?
ConnectorService::Synchronization.new(
tenant, service.data, debug
).call
end
end
end
end
What should I test on this? Should I test the return value is correct or if other method are well called?
Here is what I tried to do
require "rails_helper"
RSpec.describe ConnectorJob, type: :job do
it 'is in connector queue' do
expect(ConnectorJob.new.queue_name).to eq('connector')
end
describe 'perform' do
let (:tenant) { create(:tenant) }
let (:job) { ConnectorJob.new.perform(tenant.id) }
context 'with empty tenant' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to eq nil }
end
context 'with tenant' do
it { expect(ConnectorJob.new.perform(tenant.id)).to eq job }
end
end
end
As you can see my last test doesn't have sense but I have no idea what I should write on my Rspec for anticipate the result of this method.
If I check my Rspec coverage, Rspec is telling me I cover 100% of my method but I'm not sure that is correct.
I hope I'm clear, feel free to ask me more details.
Thank you all
I think you should test final result, I mean result after calling
ConnectorService::Synchronization.new(...).call and test three cases, e.g. if this call create new user, you should test it:
If tenant_to_sync.empty? == true
context 'with empty tenant' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(0) }
end
If service.success? == false
context 'MyAPP::ContactToSync return false' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(0) }
end
If service.success? == true
context 'success' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(1) }
end
It should be enough to cover all scenarios.

Rspec : Custom Matchers : want a matcher with behavior just opposite of existing matcher

I have a situation
let(:user) { create(:user, organization: org, role_ids: [Role::ROLE_CSA]) }
subject(:ability) { Ability.new(user) }
describe 'Not Permitted' do
# I need this
it { is_expected_not_to (be_able_to(:index, AdminsController)) }
# Or this
it { is_expected_to (not_be_able_to(:index, AdminsController)) }
end
But unfortunately I found that the is_expected_not_to and not_be_able_to are not available.
I could do in this way
it 'should not permit' do
expect(ability).not_to be_able_to(:index, AdminsController)
end
But If possible I would like to use the short form instead. Is there any way I can achieve what I want?
If not, I would like to create new custom matcher which has behavior just opposite to be_able_to of CanCan or is_expected_to if possible. can anybody help?
from: https://www.relishapp.com/rspec/rspec-core/docs/subject/one-liner-syntax
You should be able to do:
it { is_expected.not_to (be_able_to(:index, AdminsController)) }

newbie can't define a method in ruby for cucumber test pass

I am trying to learn cucumber, here's an example code from a book:
class Output
def messages
#messages ||= []
end
def puts(message)
messages << message
end
end
def output
#output ||= Output.new
end
Given /^I am not yet playing$/ do
end
When /^I start a new game$/ do
game = Codebreaker::Game.new(output)
game.start
end
Then /^I should see "([^"]*)"$/ do |message|
output.messages.should include(message)
end
When I run this spec, I get this error:
Scenario: start game # features/codebreaker_starts_game.feature:7
Given I am not yet playing # features/step_definitions/codebreaker_steps.rb:15
When I start a new game # features/step_definitions/codebreaker_steps.rb:18
Then I should see "Welcome to Codebreaker!" # features/step_definitions/codebreaker_steps.rb:23
undefined method `messages' for #<RSpec::Matchers::BuiltIn::Output:0xa86a7a4> (NoMethodError)
./features/step_definitions/codebreaker_steps.rb:24:in `/^I should see "([^"]*)"$/'
features/codebreaker_starts_game.feature:10:in `Then I should see "Welcome to Codebreaker!"'
And I should see "Enter guess:" # features/step_definitions/codebreaker_steps.rb:23
See that it gives undefined method 'messages' error, yet it is defined in the Output class.
If I replace output.messages.should with Output.new.messages.should, it works fine. What is the problem here?
Edit: Probably output is a keyword, in new version of rails, when I changed it to outputz it worked fine. An explanation of this will be accepted as an answer.
Apparently, the output matcher has been added to rspec in version 3.0:
The output matcher provides a way to assert that the has emitted
content to either $stdout or $stderr.
With no arg, passes if the block outputs to_stdout or to_stderr. With
a string, passes if the blocks outputs that specific string to_stdout
or to_stderr. With a regexp or matcher, passes if the blocks outputs a
string to_stdout or to_stderr that matches.
Examples:
RSpec.describe "output.to_stdout matcher" do
specify { expect { print('foo') }.to output.to_stdout }
specify { expect { print('foo') }.to output('foo').to_stdout }
specify { expect { print('foo') }.to output(/foo/).to_stdout }
specify { expect { }.to_not output.to_stdout }
specify { expect { print('foo') }.to_not output('bar').to_stdout }
specify { expect { print('foo') }.to_not output(/bar/).to_stdout }

Simplifying and correct RSpec controller tests

I have a few RSpec controller tests. Some work, some don't, and I'm trying to figure out how on Earth to fix them up and make them more efficient
Ideally, I would like to see if I can get each spec into the following form
subject { ... }
it { ... }
it { ... }
it { ... }
Note that for all of my controller specs I've written macros for the actual controller actions. The macros are all tested and all work, and the names make it fairly obvious what they do.
My "Create" test:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it { should redirect_to admin_customer_path(Customer.find_by_id(???))}
it { expect { subject }.to change(Customer, :count).by(1) }
end
context "invalid attributes" do
subject { do_post_create( :customer, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it { should render_template :new }
it { expect { subject }.to_not change(Customer, :count).by(1) }
end
end
In that spec, I've been trying to figure out some way to get the ID of the newly created object from the post statement. I've tried "Customer.last", but that doesn't seem to work. Any thoughts?
My "Update" spec:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
let(:object) { FactoryGirl.create(:customer) }
subject { do_put_update( class_to_symbol(model), object.id, attributes, format ) }
its(:response_code) { should eq(302)}
it "does alter #{model}" do
do_put_update( class_to_symbol(model), object.id, attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Success/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should eq(value)
end
end
end
context "invalid attributes" do
let(:object) { FactoryGirl.create("customer") }
let(:invalid_attributes) { {:username => "!"} }
subject { do_put_update( class_to_symbol(model), object.id, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it "does not alter #{model}" do
do_put_update( class_to_symbol(model), object.id, invalid_attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Fail/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should_not eq(value)
end
end
end
end
In the Update test, I would like to try to express the second block in a more concise way, ideally in a way that I can use the same "subject" statement for all of the tests. Is that possible?
I think you're over-thinking these specs. Instead of trying to force every spec into a predefined format (subject/it/...) write the specs so that they clearly document what should happen, then try to refactor the code afterwards.
Case in point: the use of the implicit subject for controller actions. subject and its are meant to be used with an object, not a method, and only really make sense when used that way. So for example, this makes sense:
subject { [1, 2, 3, 4] }
its(:size) { should == 4 }
Here, it's absolutely clear what is being tested: a 4-element array has a size of 4.
However, when you write:
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it's not really clear where you are getting that response code from without inspecting the do_post_create action. You say that the names of the macros "make it fairly obvious what they do", but they don't make it fairly obvious what they will return, and this is key for using the implicit subject because it's the return value that becomes the subject.
It would be much clearer just to write:
it "responds with a 302" do
do_post_create(:customer, valid_attributes, format)
response.should eq(302)
end
I also don't recommend mixing specs with and without implicit subjects, since it makes it yet more confusing what you are actually testing. In your invalid attributes context block, for example, you set a subject, but then in your second spec you actually test assignment of customer (assigns(:customer).should eq(object)), so basically the subject is irrelevant for this test. (However by setting the subject here and then not using it you are actually sending a PUT request twice (through do_put_update), which is bound to cause problems -- again, another reason not to be making requests in a subject block.)
I could go on, but I think you get the picture. Making specs short and sweet is great if you can do it without hurting readability, but in this case I think you've gone overboard.
Just my two cents, hope it helps.
p.s. In case the views above seem a bit extreme, read the documentation for implicit subjects, where you'll see that they actually recommend against using implicit subjects at all in public-facing tests:
While the examples below demonstrate how subject can be used as a user-facing concept, we recommend that you reserve it for support of custom matchers and/or extension libraries that hide its use from examples.

Rspec: implicitly defined subject

Rspec implicitly defined subject documentation says:
While the examples below demonstrate how subject can be used as a
user-facing concept, we recommend that you reserve it for support of
custom matchers and/or extension libraries that hide its use from
examples.
Does it mean, that I should try to never call "subject." directly in my specs? If yes, what should I use instead as a subject object?
Compare these 2 examples:
describe "User" do
subject { User.new(age: 42) }
specify { subject.age.should == 42 }
its(:age) { should == 42 }
end
describe "User" do
let(:user) { User.new(age: 42) }
specify { user.age.should == 42 }
end
UPDATE
There is a cool feature in Rspec - named subject:
Here is an example from David Chelimsky:
describe CheckingAccount, "with a non-zero starting balance" do
subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
it { should_not be_overdrawn }
it "has a balance equal to the starting balance" do
account.balance.should eq(Money.new(50, :USD))
end
end
When you use user instead of a subject it's more readable (IMHO).
But subject lets you use nice extension its(:age).

Resources