RSpec3 and message expectations - ruby-on-rails

I'm using rspec 3.0.3 and ruby 2.1.2 and just can't figure out what's going wrong.
Sorry for not good code implementation (I mean that class variables), but it was the easier way to show whats going wrong.
I have 2 classes. First calling new_method of the Test class should call AnotherTest.call_method that should change ##c_var class variable.
require "rspec"
class Test
def self.new_method
AnotherTest.call_method
end
end
class AnotherTest
##c_var = "hola"
def self.call_method
##c_var = "holla from another test"
end
def self.c_var
##c_var
end
end
And I'm writing specs for it:
describe Test do
it "should call method from an other class" do
Test.new_method
expect(AnotherTest.c_var).to be_eql("holla from another test")
end
end
And this specs is working OK. But then I'm trying to use "expect to receive call" something goes wrong
describe Test do
it "should call method from an other class" do
expect(AnotherTest).to receive(:call_method).and_return("holla from another test")
Test.new_method
expect(AnotherTest.c_var).to be_eql("holla from another test")
end
end
Failures:
1) Test should call method from an other class
Failure/Error: expect(AnotherTest.c_var).to be_eql("holla from another test")
expected `"hola".eql?("holla from another test")` to return true, got false
# ./test.rb:26:in `block (2 levels) in <top (required)>'
Seems like RSpec is making this check in something like a migration and doing a rollback after it.
It's such a strange sample, I know, but I've noticed this bug only then method of one instance of a class is calling method from the other instance and that method is trying to change something.

By using expect(...).to receive(...), the original method is not being called. Rather, when it is called, it just returns whatever you passed into and_return(...) without actually executing your method.
What you probably want is and_call_original. This way, you can ensure the method is called, and still allow it to execute:
expect(AnotherTest).to receive(:call_method).and_call_original
Source: https://www.relishapp.com/rspec/rspec-mocks/v/3-0/docs/configuring-responses/calling-the-original-implementation

Related

how to check if a method is called or not in rspec

as i try to check internally my method was calling or not in rspec but it got the following errors
context "#Meeting_schedule" do
let(:meeting_schedule) { FactoryGirl.create(:meeting_schedule,:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]) }
it "if the same schedule was created again dont save it again" do
schedule.save
params = {:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]}
meeting_schedule.create_or_update_meeting_schedule(params)
expect(meeting_schedule).to receive(:updating_the_user)
end
end
i got the following error
Failure/Error: expect(meeting_schedule.create_or_update_meeting_schedule(params)).to receive(:updating_the_user)
(#<Meeting_schedule:0x0055dbaf0da710>).updating_the_user(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/models/meeting_schedule_spec.rb:122:in `block (4 levels)
so what was wrong in my code?
my method
def create_or_update_meeting_schedule(params)
self.attributes = params
if self.changed and self.save
updating_the_user
end
self
end
can anyone help me out
Mocks must always be setup before the method under test is called as there is no reliable way to test if a normal method was called in Ruby. There are two ways of doing this in RSpec.
The first is using expect(...).to receive(...) which must be done before the method is called - this detaches the method and replaces it with a mock that wraps the original method.
The test will fail if the method is not called in the example.
The second is by using spies. You can either replace an entire object with a spy:
RSpec.describe "have_received" do
it "passes when the message has been received" do
invitation = spy('invitation')
invitation.deliver
expect(invitation).to have_received(:deliver)
end
end
This "spy object" will keep track of any method you call on it.
You can also spy on a single method:
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
it "passes when the expectation is met" do
allow(Invitation).to receive(:deliver)
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end
Spies are very useful in the case when you want to mock the method in the test setup - for example in the before block.
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
before do
allow(Invitation).to receive(:deliver)
end
it "passes when the expectation is met" do
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end

Rspec passes class method but fails instance method

I'm trying to write a failing Rspec test. The actual test is associated with much longer code, but I narrowed down the problem to the class method it's testing.
Here's the test in Rspec:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
Stock.change_to_error
expect(cash.status).to eq('Available')
end
end
end
This is testing a model class method in Stock.rb:
def self.change_to_error
self.all.each do |stock|
stock.status = "Error"
stock.save
end
end
For some reason, this passes. However, if I changed it to use an instance method, it will fail like it should:
If stock_spec.rb changed to instance method:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
cash.change_to_error
expect(cash.status).to eq('Available')
end
end
end
And if stock.rb class method turned into an instance method:
def change_to_error
self.status = 'Error'
self.save
end
This would pass. Unfortunately, I have to use a class method instead of an instance method because I want to update all stocks in the DB. "Change_to_error" methods are just there to figure out the problem. Does anyone know why it passes as a class method when it should fail? But it fails correctly when it's using an instance method?
Effectively, what is happening is that the class method does not change the status attribute of 'cash', but the instance method does. I don't know why that is happening.
FYI, I'm using rspec-rails
Solution: Need to put 'cash.reload' after 'Stock.change_to_error' and before the expect line.
When using let! the object is created before the test. Updating the underlying data outside the object causes the instance to be outdated. Calling reload on it forces ActiveRecord to refresh it from the database.
When you use let, RSpec does not call the block until the first time you reference the attribute, in this case, cash. So in your first example, you're running change_to_error on no records at all and then checking the status on cash, a record that gets created on the line with expect. In your second example, the cash object is created, then changed to an error. I'd recommend tailing your log to confirm this (tail -f log/test.log)
If you change to let!, RSpec will create the object before every example is run. Another alternative is to reference cash in your example before calling change_to_error on all records that are created.

How do I test Rails logging from an Rspec feature spec?

This question tells me how to test logger statements from RSpec model and controller specs. Unfortunately, it doesn't seem to work from a feature spec. With this code:
# controller.rb
def action
logger.info 'foobar'
end
# spec.rb
scenario 'logging should work' do
expect(Rails.logger).to receive(:info).with('foobar')
visit action_path
end
I get the error:
Failure/Error: visit action_path
#<ActiveSupport::Logger:0x007ff45b6e5ad0> received :info with unexpected arguments
expected: ("foobar")
got: (no args)
The test.log file does not contain foobar, so it seems the test is failing immediately, before the controller action has a chance to complete.
Is there some way to use this expect(Rails.logger) syntax in a feature spec?
The Rails.logger.info method can take a string or a block. If you're invoking the block form then it will give this "got: (no args)" output.
For example
logger.info 'foobar'
...all on one line will call .info with a string, but if you do
logger.info
"foobar foobar longer message so I'll put it on its own line"
split across two lines without brackets, then you're actually passing a block. Add some brackets...
logger.info(
"foobar foobar longer message so I'll put it on its own line"
)
...and you're back to a string.
He says knowingly after bashing his head on this problem for a few hours :-)
Before realising that, I started figuring out how to mock the Rails.logger class. That might be a useful approach for you or others. Maybe you're calling with a block for some other reason (something to do with feature vs controller specs?), or maybe you can't change the calling code, in which case... something like this might be a useful starting point:
class LoggerMock < Object
def initialize; end
def info(progname = nil, &block)
mock_info(block.call)
end
end
and
logger_mock = LoggerMock.new
allow(Rails).to receive(:logger).and_return(logger_mock)
expect(logger_mock).to receive(:mock_info).with('foobar')

Having trouble with Rspec expect(double).to receive(:message)

Having been inspired by Sandi Metz's approach to writing tests (http://www.confreaks.com/videos/2452-railsconf2013-the-magic-tricks-of-testing), I am trying to refactor a test for a Rails controller to assert that it is sending a command message properly.
Here are the relevant parts of the Application:
class DealsController < ApplicationController
def index
if params[:reset]
deal_filter.reset
...
class ApplicationController
def deal_filter
...
#deal_filter ||= DealFilter.new(args)
end
...
class DealFilter
def reset
...do work...
end
...
And here is the rspec test:
describe DealsController do
it "should send 'reset' to the deal_filter" do
df = instance_double("DealFilter")
get :index, reset: "true"
expect(df).to receive(:reset)
end
end
The test results that keep coming back are:
1) DealsController GET index for any user params contain 'reset' should send 'reset' to the deal_filter
Failure/Error: expect(df).to receive(:reset)
(Double "DealFilter (instance)").reset(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
I have already confirmed that the reset param is being sent through the test and that the controller is following the appropriate path, yet the test continues to fail.
Can anyone suggest a possible reason for the failure or resources for further study? I am relatively new to object oriented thinking and using mocks with Rspec. Could it be that I have misunderstood the role of doubles?
Thanks for your time!
You need to make sure your double gets used. I think the best way to do that here is to stub the deal_filter method to return the double.
I addition I would isolate the expection, so that it's the only thing in the it block. This will make it easier to add more expections without duplication the setup logic.
describe DealsController do
let(:df) { instance_double("DealFilter") }
before do
allow(controller).to receive(:deal_filter).and_return(df)
get :index, reset: "true"
end
it "should send 'reset' to the deal_filter" do
expect(df).to have_received(:reset)
end
end
I think you're expecting your instance_double to be used automatically somewhere within the index action. That's not how doubles work. You can create a double and use it for things, but your code in the controller doesn't (and shouldn't) know anything about that double and so won't ever call anything on it.
For an example of how an instance double can actually be used see this documentation.
Another issue with your expectation is that you're not setting it early enough. When you expect an object to receive a method call there needs to be something that happens after that which would invoke that method. In your example the expectation to receive :reset is the very last line of your example.
I'd recommend reading up on how other people have tested controllers with rspec as a good starting place.

Setting expectation on resque .perform method. Task is enqueued in a Callback

So based on my understanding, I beleive when you do
Resque.inline = Rails.env.test?
Your resque tasks will run synchronously. I am writing a test on resque task that gets enqueue during an after_commit callback.
after_commit :enqueue_several_jobs
#class PingsEvent < ActiveRecord::Base
...
def enqueue_several_jobs
Resque.enqueue(PingFacebook, self.id)
Resque.enqueue(PingTwitter, self.id)
Resque.enqueue(PingPinterest, self.id)
end
In the .perform methd of my Resque task class, I am doing a Rails.logger.info and in my test, I am doing something like
..
Rails.logger.should_receive(:info).with("PingFacebook sent with id #{dummy_event.id}")
PingsEvent.create(params)
And I have the same test for PingTwitter and PingPinterest.
I am getting failure on the 2nd and third expectation because it seems like the tests actually finish before all the resque jobs get run. Only the first test actually passes. RSpec then throws a MockExpectationError telling me that Rails.logger did not receive .info for the other two tests. Anyone has had experience with this before?
EDIT
Someone mentioned that should_receive acts like a mock and that I should do .exactly(n).times instead. Sorry for not making this clear earlier, but I have my expectations in different it blocks and I don't think a should_receive in one it block will mock it for the next it block? Let me know if i'm wrong about this.
class A
def bar(arg)
end
def foo
bar("baz")
bar("quux")
end
end
describe "A" do
let(:a) { A.new }
it "Example 1" do
a.should_receive(:bar).with("baz")
a.foo # fails 'undefined method bar'
end
it "Example 2" do
a.should_receive(:bar).with("quux")
a.foo # fails 'received :bar with unexpected arguments
end
it "Example 3" do
a.should_receive(:bar).with("baz")
a.should_receive(:bar).with("quux")
a.foo # passes
end
it "Example 4" do
a.should_receive(:bar).with(any_args()).once
a.should_receive(:bar).with("quux")
a.foo # passes
end
end
Like a stub, a message expectation replaces the implementation of the method. After the expectation is fulfilled, the object will not respond to the method call again -- this results in 'undefined method' (as in Example 1).
Example 2 shows what happens when the expectation fails because the argument is incorrect.
Example 3 shows how to stub multiple invocations of the same method -- stub out each call with the correct arguments in the order they are received.
Example 4 shows that you can reduce this coupling somewhat with the any_args() helper.
Using should_receive behaves like a mock. Having multiple expectations on the same object with different arguments won't work. If you change the expectation to Rails.logger.should_receive(:info).exactly(3).times your spec will probably past.
All that said, you may want to assert something more pertinent than what is being logged for these specs, and then you could have multiple targeted expectations.
The Rails.logger does not get torn down between specs, so it doesn't matter if the expectations are in different examples. Spitting out the logger's object id for two separate examples illustrates this:
it 'does not tear down rails logger' do
puts Rails.logger.object_id # 70362221063740
end
it 'really does not' do
puts Rails.logger.object_id # 70362221063740
end

Resources