rspec mock question - ruby

I am trying to Mock an object that is being passed into another object, and am having no success. Can someone show me what I am doing wrong?
class Fetcher
def download
return 3
end
end
class Reports
def initialize(fetcher)
#fetcher = fetcher
end
def status
#fetcher.download
end
end
describe Reports do
before(:each) do
end
it "should get get status of 6" do
Fetcher.should_receive(:download).and_return(6)
f = Reports.new(Fetcher.new)
f.status.should == 6
end
end
The spec still reports status returning 3, not my intended 6.
Certainly I am missing something here. Any thoughts?

In the test, what I think you're trying to do is this (I think)
it '...' do
some_fetcher = Fetcher.new
some_fetcher.should_receive(:download).and_return(6)
f = Reports.new(some_fetcher)
f.status.should == 6
end
when you say Fetcher.should_receive(:download), you're saying the CLASS should receive the call 'download', instead of INSTANCES of the class...
Hope this helps! If it's not clear, just let me know!

An updated based on the new syntax.
subject(:reports) { described_class.new(fetcher) }
let(:fetcher} { double("Fetcher") }
describe "#status" do
it "gets status of 6" do
fetcher.stub(download: 6)
expect(reports.status).to == 6
end
end

Related

Making a method return a specific value (that would otherwise be random) in Rspec

I'm trying to write an rspec test that would assume a method will return a specific value when it would normally return a random number between 1 and 10.
Here is the code in my lib directory:
def take_off(plane)
fail "Bad weather today. Cannot take off." if stormy_weather? > 8
plane.take_off_plane
planes.delete(plane)
end
def stormy_weather?
rand(10)
end
And here is my test in Rspec:
it 'raises an error when weather is stormy' do
plane = double(:plane)
stormy_weather = 9
allow(plane).to receive(:take_off_plane)
expect{ subject.take_off(plane) }.to raise_error("Bad weather today. Cannot take off.")
end
Thank you in advance to anyone who helps!
You can specify the returned value with and_return:
allow(subject).to receive(:stormy_weather?).and_return(9)

NilCheck fix on safe navigation operator (&.)

This simple method on a class just run the status method using the safe navigation operator.
def current_status
account&.status
end
But reek report this warning:
MyClass#current_status performs a nil-check [https://github.com/troessner/reek/blob/master/docs/Nil-Check.md]
How can I properly write methods like this to avoid Nil Check?
I've also verified this post from thoughtbot but it seem like "too much" for just a safe navigation operator.
Ruby 2.3.1
The advice from "Example 4" in the linked post is verbose but pretty good :
class MyClass
def initialize(with_account = nil)
#account = Account.new if with_account
end
def current_status
account.status
end
def account
#account || NilAccount.new
end
end
class Account
def status
"Up!"
end
end
class NilAccount
def status
"Down!"
end
end
puts MyClass.new(:with_account).current_status
#=> "Up!"
puts MyClass.new.current_status
#=> "Down!"
If it's "too much" for you, account&.status might be just fine.
Whatever you do : you'll need to test your code as much as possible!
well, tell-dont-ask looks pretty good, but Example 4 looks like an overkill to resolve this specific case.
#andredurao I think, we can use this workaround to pass checks, for some reason reek is fine with it:
def current_status
return unless account
account.status
end

Rails 4 validation not working

I have a model which I would like to validate the name if it is part of an array that I get from somebody else's API.
class Model < ActiveRecord::Base
validate :exists_at_api?
def exists_at_api?
api_data.detect { |d| d == self.name }
end
end
The problem occurs when I send invalid data
The validation gets called, and returns false, but the model is still saved.
I also tried this variation of the above with the same results:
validate :name, if: :exists_at_api?
I'm sure this is something simple, can somebody point me in the right direction?
You need to add something to the errors hash to indicate the failure. See the Rails documentation for details and examples.
Try something like:
validate :exists_at_api?
def exists_at_api?
if api_data.detect { |d| d == self.name }
errors.add(:name, "can't be whatever...")
end
end

How can I figure out which step I've just executed in Cucumber's AfterStep hook?

I'm writing a method to be executed on the AfterStep callback for Cucumber.
https://github.com/cucumber/cucumber/wiki/Hooks#step-hooks
How can I figure out which step was executed before this hook was called?
Using gem cucumber 2.1.0 and scenario outlines, the scenario object in "Afterstep" is just a test result status, it does not contain the name of the step. I had to use "Before" (called before the first step) that contains a test list.
require 'logger'
$logger = Logger.new(STDOUT)
Before do |scenario|
#count = 0
#tests = Array.new
scenario.test_steps.each{|r|
if( r.name != "AfterStep hook")
#tests << r
end
}
end
AfterStep do |scenario| # run after each step
$logger.info(#tests[#count].name.green)
#count += 1;
end
The logger is required because 'puts' only display when the scenario outline ends.
The AfterStep hook only receives the scenario as parameter.
What you can do, is count the steps, and then get the current one:
AfterStep do |scenario|
#step ||= 0
p scenario.steps[#step].name
#step += 1
end
This will print, in turn, the names of each parameter
Note:
The api has changed slightly. you now need to use 'to_a'
i.e. the Alex Siri's line above would be changed to:
p scenario.steps.to_a[#step].name
Vince has a good solution, I would recommend a refactor:
Before do |scenario|
#tests = scenario.test_steps.map(&:name).delete_if { |name| name == 'AfterStep hook' }
end
You can use the #tests.count instead of the #count variable
I would have made this as comment but I don't have enough reputation yet.
The API has been changed... Based on afterhook doc you can get the result (Cucumber::Core::Test::Result) and the step (Cucumber::Core::Test::Step) like this:
AfterStep do |result, test_step|
#do something
end
You can get the step name with:
stepName = test_step.text
or
stepName = test_step.to_s
I worked it out as follows:
Before do |scenario|
...
#scenario = scenario
#step_count = 0
...
end
AfterStep do |step|
#step_count += 1
end
That keeps the step number updated. In order to get the step name:
#scenario.test_steps[#step_count].name
Vince's answer is great! and SMAG's refactor is cool ! but when I applied the solution on my cucumber test project, I got an error:
undefined method `name' for #<Cucumber::Core::Test::Step:>
so, maybe the answer can update as below:
Before do |scenario|
#tests = scenario.test_steps.map(&:text).delete_if { |text| text == 'AfterStep hook' }
end

Unit Testing Ruby Blocks by Mocking with rr (was flexmock)

How do I unit test the following:
def update_config
store = YAML::Store.new('config.yaml')
store.transaction do
store['A'] = 'a'
end
end
Here is my start:
def test_yaml_store
mock_store = flexmock('store')
mock_store
.should_receive(:transaction)
.once
flexmock(YAML::Store).should_receive(:new).returns(mock_store)
update_config()
end
How do I test what is inside the block?
UPDATED
I have converted my test to spec and switched to rr mocking framework:
describe 'update_config' do
it 'calls transaction' do
stub(YAML::Store).new do |store|
mock(store).transaction
end
update_config
end
end
This will test the transaction was called. How do I test inside the block: store['A'] = 'a'?
First, you can write this a little simpler -- your test using RR isn't a direct port of your test using FlexMock. Second, you're not testing what happens within the block at all so your test is incomplete. Try this instead:
describe '#update_config' do
it 'makes a YAML::Store and stores A in it within a transaction' do
mock_store = {}
mock(mock_store).transaction.yields
mock(YAML::Store).new { mock_store }
update_config
expect(mock_store['A']).to eq 'a'
end
end
Note that since you're providing the implementation of #transaction, not merely the return value, you could have also said it this way:
describe '#update_config' do
it 'makes a YAML::Store and stores A in it within a transaction' do
mock_store = {}
mock(mock_store).transaction { |&block| block.call }
mock(YAML::Store).new { mock_store }
update_config
expect(mock_store['A']).to eq 'a'
end
end
You want to call yields:
describe 'update_config' do
it 'calls transaction which stores A = a' do
stub(YAML::Store).new do |store|
mock(store).transaction.yields
mock(store).[]=('A', 'a')
end
update_config
end
end
Check out this answer for a different approach to a related question. Hopefully the rr api documentation will improve.

Resources