In rspec 3.2, I have something based on the following pseudocode:
context 'my test context' do
before do
method_that_uses(error_message)
end
subject { post :my_action, params: a_bunch_of_params }
let(:error_message) { 'error' }
it { is_expected.to raise_error(MyException) }
let(:error_message) { 'different error' }
it { is_expected.to redirect_to(a_path) }
let(:error_message) { 'third error' }
it { is_expected.to redirect_to(another_path) }
end
Every example runs with error_message set to third error. I confirmed this by running pry from the before hook as well. How can I get the desired behavior?
That happens because internally, let uses define_method as seen in the source for let. You can create a quick example
class A
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
end
a = A.new
a.create_method(:foo) { puts "bar" }
a.create_method(:foo) { puts "baz" }
a.foo
and run that and you would see that define_method overrides the previous method with the new one. So in your example, you are creating a method and then overriding it's definition twice before you ever get a chance to call it.
You are wanting to run each error_message in it's own context like this:
def method_that_uses(e)
puts "running with: #{e}"
end
context 'my test context' do
before do
method_that_uses(error_message)
end
context 'error' do
let(:error_message) { 'error' }
it { puts "run one" }
end
context 'second error' do
let(:error_message) { 'different error' }
it { puts "run two" }
end
context' third error' do
let(:error_message) { 'third error' }
it { puts "run three" }
end
end
which, when run, outputs
running with: error
run one
.running with: different error
run two
.running with: third error
run three
.
This works because describe and context blocks create a new ExampleGroup (source) and ExampleGroup states
Example group bodies (e.g. describe or context blocks) are evaluated in the context of a new subclass of ExampleGroup.
So, let(:error_message) is now defining those methods on different subclasses.
Related
I want to test a rails job which call an endpoint of hubspot API (GET /crm/v3/owners/{ownerId}) and update a record with infos of the request result.
The problem is that I use this gem as an API wrapper and my before block seems like ignored because the result shows that the API call can't give me an owner object with this owner_id (the one given as parameter of attributes is obviously fake). A before block is supposed to override the "normal" response of the controller isn't it ?
I really don't know what I am doing wrong ..
For more context:
My job code
module Hubspots
module Contracts
class UpdateJob < BaseJob
queue_as :high_priority
def perform(attributes)
contract = Contract.find_by(hubspot_sales_deal_id: attributes[:hubspot_sales_deal_id])
return if contract.nil?
deal_owner = client.crm.owners.owners_api.get_by_id(owner_id: attributes[:hubspot_tailor_deal_owner],
id_property: 'id', archived: false)
attributes[:hubspot_tailor_deal_owner] = get_owner_name(deal_owner)
contract.update!(attributes)
end
private
def get_owner_name(hubspot_owner_object)
"#{hubspot_owner_object.last_name.upcase} #{hubspot_owner_object.first_name.capitalize}"
end
end
end
end
My test code
RSpec.describe Hubspots::Contracts::UpdateJob, type: :job do
let!(:job) { described_class.new }
let(:perform) { job.perform(attributes) }
let!(:contract) { create(:contract, hubspot_sales_deal_id: 123) }
let!(:attributes) do
{ hubspot_tailor_deal_id: 456, hubspot_tailor_deal_owner: 876, hubspot_sales_deal_id: 123 }
end
let!(:deal_owner_api) { Hubspot::Client.new(access_token: ENV['HUBSPOT_ACCESS_TOKEN']).crm.owners.owners_api }
let!(:deal_owner_properties) { { last_name: 'Doe', first_name: 'John' } }
before do
allow(deal_owner_api).to receive(:get_by_id).and_return(deal_owner_properties)
end
describe '#perform' do
it 'updates contract' do
expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }.from(nil)
.to('DOE John')
end
end
end
Test result
I try to rtfm on google but I didn't find the solution yet (I'm always bad for rtfm btw)
Ok my CTO finaly gave me the solution by using the Webmock gem
The code snippet :
before do
stub_request(:get, 'https://api.hubapi.com/crm/v3/owners/876?archived=false&idProperty=id')
.with(headers: { 'Authorization' => "Bearer #{ENV['HUBSPOT_ACCESS_TOKEN']}" }).to_return(status: 200, body: {
firstName: 'John',
lastName: 'Doe'
}.to_json, headers: {})
end
describe '#perform' do
it 'updates contract' do
expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }.from(nil)
.to('DOE John')
end
end
I have a class, that in one situation should call :my_method, but in another situation must not call method :my_method. I would like to test both cases. Also, I would like the test to document the cases when :my_method should not be called.
Using any_instance is generally discouraged, so I would be happy to learn a nice way to replace it.
This code snippet is a reduced example on what I kind of test I would like to write.
class TestSubject
def call
call_me
end
def call_me; end
def never_mind; end
end
require 'rspec'
spec = RSpec.describe 'TestSubject' do
describe '#call' do
it 'calls #call_me' do
expect_any_instance_of(TestSubject).to receive(:call_me)
TestSubject.new.call
end
it 'does not call #never_mind' do
expect_any_instance_of(TestSubject).not_to receive(:never_mind)
TestSubject.new.call
end
end
end
spec.run # => true
It works, but uses expect_any_instance_of method, which is not recommended.
How to replace it?
I'll do somehting like that
describe TestSubject do
describe '#call' do
it 'does not call #something' do
subject = TestSubject.new
allow(subject).to receive(:something)
subject.call
expect(subject).not_to have_received(:something)
end
end
end
Hope this helped !
This is how I normally unit-test. I updated the code to support other possible questions you (or other readers) may have in the future.
class TestSubject
def call
some_call_me_value = call_me
call_you(some_call_me_value)
end
def call_me; end
def call_you(x); end
def never_mind; end
class << self
def some_class_method_a; end
def some_class_method_b(x, y); end
end
end
require 'rspec'
spec = RSpec.describe TestSubject do
context 'instance methods' do
let(:test_subject) { TestSubject.new }
describe '#call' do
let(:args) { nil }
let(:mocked_call_me_return_value) { 'somecallmevalue' }
subject { test_subject.call(*args) }
before do
allow(test_subject).to receive(:call_me) do
mocked_call_me_return_value
end
end
it 'calls #call_me' do
expect(test_subject).to receive(:call_me).once
subject
end
it 'calls #call_you with call_me value as the argument' do
expect(test_subject).to receive(:call_you).once.with(mocked_call_me_return_value)
subject
end
it 'does not call #never_mind' do
expect(test_subject).to_not receive(:never_mind)
subject
end
it 'calls in order' do
expect(test_subject).to receive(:call_me).once.ordered
expect(test_subject).to receive(:call_you).once.ordered
subject
end
end
describe '#call_me' do
let(:args) { nil }
subject { test_subject.call_me(*args) }
# it ...
end
describe '#call_you' do
let(:args) { nil }
subject { test_subject.call_you(*args) }
shared_examples_for 'shared #call_you behaviours' do
it 'calls your phone number'
it 'creates a Conversation record'
end
# just an example of argument-dependent behaviour spec
context 'when argument is true' do
let(:args) { [true] }
it 'does something magical'
it_behaves_like 'shared #call_you behaviours'
end
# just an example of argument-dependent behaviour spec
context 'when argument is false' do
let(:args) { [false] }
it 'does something explosive'
it_behaves_like 'shared #call_you behaviours'
end
end
end
context 'class methods' do
let(:args) { nil }
describe '#some_class_method_a' do
let(:args) { nil }
subject { TestSubject.some_class_method_a(*args) }
# it ...
end
describe '#some_class_method_b' do
let(:args) { [1, 2] }
subject { TestSubject.some_class_method_b(*args) }
# it ...
end
end
end
spec.run # => true
Do not test if some method was called or wasn't.
This will tight your tests to the implementation details and will force you to change tests every time you refactor(change implementation details without changing the behaviour) your class under test.
Instead test against return value or changed application state.
It is difficult come up with the example, you didn't provide enough context about the class under the test.
class CreateEntity
def initialize(name)
#name = name
end
def call
if company_name?(#name)
create_company
else
create_person
end
end
def create_person
Person.create!(:name => #name)
end
def create_company
Company.create!(:name => #name)
end
end
# tests
RSpec.describe CreateEntity do
let(:create) { CreateEntity.new(name).call }
describe '#call' do
context 'when person name is given' do
let(:name) { 'Firstname Lastname' }
it 'creates a person' do
expect { create }.to change { Person.count }.by(1)
end
it 'do not create a company' do
expect { create }.not_to change { Company.count }
end
end
context 'when company name is given' do
let(:name) { 'Name & Sons Ltd' }
it 'creates a company' do
expect { create }.to change { Company.count }.by(1)
end
it 'do not create a person' do
expect { create }.not_to change { Person.count }
end
end
end
end
With tests above I would be able to change how CreateEntity.call method implemented without changing tests as far as behaviour remain same.
I've got a before_destroy callback that looks like this:
class Component < ActiveRecord::Base
has_many :documents, through: :publications
def document_check
if documents.exists?
errors[:documents] << 'cannot exist'
return true
else
return false
end
end
The test looks like this:
describe '#document_check' do
let(:document) { create(:document) }
let(:component) { create(:component) }
context 'with documents' do
before do
document.components << component
end
specify { expect(component.errors).to include(:document, 'cannot exist') }
specify { expect(component.document_check).to eq true }
end
context 'without documents' do
before do
document.components = []
end
specify { expect(component.document_check).to eq false }
end
end
I want it to raise the error if a component is in a document, but I can't seem to be able to write it correctly. The second test passes, the first doesn't:
Diff:
## -1,2 +1,2 ##
-[:document, "cannot exist"]
+[]
What am I doing wrong?
How is document_check being invoked? If manually (as you're 2nd tests seem to suggest) then you also need to invoke it for the first specify.
That is:
specify { component.document_check; expect(component.errors).to include(:document, 'cannot exist') }
That's horrible syntax, but you need to invoke the method before you can check the errors on it.
Here's the callback:
def document_check
return unless documents.present?
errors.add(:article, 'in use cannot be deleted')
false
end
And here's the passing test for it.
describe '#document_check' do
let(:subject) { create(:component) }
let(:document) { create(:document) }
let(:count) { Component.size }
before do
document.components << subject
subject.send :document_check
end
context 'with documents raises error' do
specify do
expect(subject.errors[:article]).to be_present
end
end
context 'with documents raises correct error' do
specify do
expect(subject.errors[:article]).to include(
'in use cannot be deleted')
end
end
context 'with documents prevents deletion' do
specify do
expect { subject.destroy }.to_not change(Component, :count)
end
end
end
Took ages but it's worth it.
I have an object MyObject:
class MyObject
def initialize(options = {})
#stat_to_load = options[:stat_to_load] || 'test'
end
def results
[]
end
end
I want to stub the results method only if stat_to_load = "times". How can I do that? I tried:
MyObject.any_instance.stubs(:initialize).with({
:stat_to_load => "times"
}).stubs(:results).returns(["klala"])
but it does not work. Any idea?
So, I think there is probably a simpler way to test what you're trying to test, but without more context I don't know what to recommend. However, here is some proof-of-concept code to show that what you want to do can be done:
describe "test" do
class TestClass
attr_accessor :opts
def initialize(opts={})
#opts = opts
end
def bar
[]
end
end
let!(:stubbed) do
TestClass.new(args).tap{|obj| obj.stub(:bar).and_return("bar")}
end
let!(:unstubbed) { TestClass.new(args) }
before :each do
TestClass.stub(:new) do |args|
case args
when { :foo => "foo" }
stubbed
else
unstubbed
end
end
end
subject { TestClass.new(args) }
context "special arguments" do
let(:args) { { :foo => "foo" } }
its(:bar) { should eq "bar" }
its(:opts) { should eq({ :foo => "foo" }) }
end
context "no special arguments" do
let(:args) { { :baz => "baz" } }
its(:bar) { should eq [] }
its(:opts) { should eq({ :baz => "baz" }) }
end
end
test
special arguments
bar
should == bar
opts
should == {:foo=>"foo"}
no special arguments
bar
should == []
opts
should == {:baz=>"baz"}
Finished in 0.01117 seconds
4 examples, 0 failures
However I'm making a lot of use of special subject/let context blocks here. See http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/ for more on that subject.
Try out below, this should work as expected:
Here, Basically we are actually stubbing new instance getting created and also stubbing results method of the instance which is getting returned.
options = {:stat_to_load => "times"}
MyObject.stubs(:new).with(options)
.returns(MyObject.new(options).stubs(:results).return(["klala"]))
You could use plain old Ruby inside your test to achieve this.
MyObject.class_eval do
alias_method :original_results, :results
define_method(:results?) do
if stats_to_load == "times"
["klala"]
else
original_results
end
end
end
I have this module:
module TicketsPresenters
class ShowPresenter
def initialize(ticket_or_id = nil)
unless ticket_or_id.nil?
if ticket_or_id.is_a?(Ticket)
#ticket = ticket_or_id
else
#ticket = Ticket.find(ticket_or_id)
end
end
end
end
I'd like to test if the initialize() method sets up the object properly, when I pass an integer or directly the object instance.
There's probably a dozen ways to answer this, but I'll give you the RSpec format that I prefer.
The following assumes you have a reader method for ticket (ie attr_reader :ticket) in your ShowPresenter class. It also assumes you are creating your Ticket object with valid parameters so it gets saved.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let!(:ticket) { Ticket.create!(...) }
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end
Note: I'm a fan of FactoryGirl so I would personally prefer to use Factory.create(:ticket) over Ticket.create!(...) since it allows you define a valid Ticket object in one place and you don't need to update it in all your tests if that definition changes.
Another testing position that people take is to not use database persistance at all. This is probably not a concept I would suggest to people new to Ruby or RSpec since it is a little harder to explain and would require more OOP knowledge. The upside is that it removes the database dependency and tests are faster and more isolated.
describe TicketsPresenters::ShowPresenter do
context '#initialize' do
let(:ticket) { mock(:ticket, id: 1) }
before do
ticket.stub(:is_a?).with(Ticket) { true }
Ticket.stub(:find).with(ticket.id) { ticket }
end
context 'with an id' do
subject { TicketsPresenters::ShowPresenter.new(ticket.id) }
its(:ticket) { should == ticket }
end
context 'with a Ticket object' do
subject { TicketsPresenters::ShowPresenter.new(ticket) }
its(:ticket) { should == ticket }
end
context 'with nothing' do
subject { TicketsPresenters::ShowPresenter.new }
its(:ticket) { should be_nil }
end
end
end