Rspec Mock Object Example

RSpec Mock Object Example

Here's an example of a simple mock I did for a controller test in a rails application:

before(:each) do
@page = mock_model(Page)
@page.stub!(:path)
@page.stub!(:find_by_id)
@page_type = mock_model(PageType)
@page_type.stub!(:name)
@page.stub!(:page_type).and_return(@page_type)
end

In this case, I'm mocking the Page & PageType models (Objects) as well as stubbing out a few of the methods I call.

This gives me the ability to run a tests like this:

it "should be successful" do
Page.should_receive(:find_by_id).and_return(@page)
get 'show', :id => 1
response.should be_success
end

I know this answer is more rails specific, but I hope it helps you out a little.


Edit

Ok, so here is a hello world example...

Given the following script (hello.rb):

class Hello
def say
"hello world"
end
end

We can create the following spec (hello_spec.rb):

require 'rubygems'
require 'spec'

require File.dirname(__FILE__) + '/hello.rb'

describe Hello do
context "saying hello" do
before(:each) do
@hello = mock(Hello)
@hello.stub!(:say).and_return("hello world")
end

it "#say should return hello world" do
@hello.should_receive(:say).and_return("hello world")
answer = @hello.say
answer.should match("hello world")
end
end
end

Mocking object that are generated dynamically + rspec-mock

Instances of the Java::RandomRequest and Java::RandomResponse that you create in spec do not match those that you create in create_address.
Try stubbing the classes to return the values you need like:

let(:request) { instance_double(Java::RandomRequest) }
let(:response) { instance_double(Java::RandomResponse) }
before do
allow(Java::RandomRequest).to receive(:new).and_return(request)
allow(Java::RandomResponse).to receive(:new).and_return(response)
end

Also, you can collapse allow in before with expect... have_received after create_address call to a spy called before the create_address action, e.g.:

# no before block
it 'should create the address' do
expect(AddressCreator.singleton)
.to receive(:create_address)
.with(some_key, request, response) do |request|
expect(request.is_success).to eq(response.is_success)
end
result = AddressCreator.instance.client.create_address some_key
expect(result).to eq(response)
end

Another option would be to use kind_of/instance_of/duck_type argument matchers instead of stubs if you don't intend to check how exactly request and response are initialized, e.g. something like:

# no before
it 'should create the address' do
expect(AddressCreator.singleton).to receive(:create_address)
.with(kind_of(String), instance_of(Java::RandomRequest), duck_type(:is_success)) do |request|
expect(request.is_success).to be_true
end
AddressCreator.instance.client.create_address some_key
end

Lastly, you don't have to initialize some_key with real secure key, double would be enough if you don't process and just pass to some stubbed class in the end:

let(:some_key) { double }

How to properly mock objects in RSpec?

bucket and key parameters differ in actual calling and mocking.
Use below code it works:

describe S3DownloadUrlGenerator do
before do
allow(Aws::S3::Client).to receive(:new) { s3_client }
end
let(:s3_client) { spy("s3 client") }
let(:presigner) { spy("s3 presigner") }
it "generates download URL for a file" do
expect(Aws::S3::Presigner).to receive(:new).with(client: s3_client).and_return(presigner)
expect(presigner).to receive(:presigned_url).with(
:get_object,
bucket: "my-bucket",
key: "Test_file.txt",
response_content_disposition: "attachment",
).and_return("https://www.example.com")
expect(described_class.new("Test_file.txt").presigned_url).to eq("https://www.example.com")
end
end

With rspec, can I mock an object that is inside the method I am testing?

You can indeed mock out other methods in a RSpec test. If the two methods you mentioned are inside a class, Foo, you would do something like this to make sure that some_other_method is called:

subject{ Foo.new }
it "should do whatever you're testing" do
subject.should_receive(:some_other_method).and_return(5)
subject.some_method
end

If you don't need to assert that is was called, just assert the results of some_method, you can do something like this instead:

subject{ Foo.new }
it "should do whatever you're testing" do
subject.stub(:some_other_method).and_return(5)
subject.some_method.should eq(6)
end

The above examples assume you're using RSpec 2. If you're using RSpec 1, you'll need to use stubs instead of stub.

If your methods are defined outside of a class, they're really defined on the class Object, so just use Object instead of Foo in the examples above.

For more information about mocks in RSpec, check out http://relishapp.com/rspec/rspec-mocks for RSpec 2 or http://rspec.info/documentation/mocks/ for RSpec 1.

How to properly mock itnernal services in RSpec?

With rspec-mocks gem, you can use allow_any_instance_of. Usually, this part lies in before block.

In fact,Action::PartsShow is responsible for loading a part, so there is no need to leak two instance methods: call and part. You can simplify it through returning the part from call.

module Action
class PartsShowBase
#attr_reader :part

def call
find_part # assign @part
reload_part_availability
reload_part_price if @current_user.present?
@part
end
...
end
RSpec.describe PartController, type: :request do
before :all do
allow_any_instance_of(Action::PartsShow).to receive(:call).and_return(returned_part)
end

Reference

https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/working-with-legacy-code/any-instance

How to stub a third party object using rspec-mock?

The code you've included within config.before(:each) do will execute before each test - no sooner. However, your initializer's code will execute once, before any of the test cases are executed.

This means that instance of Hominid::API stored in MAILCHIMP wasn't affected by the instruction to stub Hominid::API.new - it's still fully functional.

You should place the stub on MAILCHIMP as well:

config.before(:each) do
allow_any_instance_of(Hominid::API).to receive(:list_subscribe).and_return(true)
allow(MAILCHIMP).to receive(:list_subscribe).and_return(true)
end

How do I mock a class using Rspec and Rails?

rspec mocks and stubs can be used on any class.
For example:

coinbase_mock = double(api_key: ENV['COINBASE_KEY'], api_secret: ENV['COINBASE_SECRET'])
expect(Coinbase::Wallet::Client).to_receive(:new).and_return(coinbase_mock)

then you can add whatever you like to the coinbase_mock so that it quacks like the class you need... :)

How to stub a class method using rspec/rspec-mocks

For your workflow, I think it's going to work better to use a class_double than than to stub the Hashes class directly. allow(Hashes) is always going to require that the Hashes constant is defined. It's simply how Ruby works and RSpec can't do anything about that. With a class double, you can instead do this:

class_double("Hashes", :calculate_hash => canned_return_value).as_stubbed_const

# or

hashes = class_double("Hashes").as_stubbed_const
allow(hashes).to receive(:calculate_hash) do |file|
# look up what to return
end

class_double("Hashes") provides you with a test double that, when the Hashes constant is defined, will verify the mocked and stubbed methods against the Hashes class definition, but when it is not defined, will act just like a normal double that allows anything to be mocked or stubbed on it. The as_stubbed_const bit tells rspec-mocks to stub the Hashes constant for the duration of the example so that any references to Hashes get your class double rather than the real Hashes class, even if the Hashes class has never been defined.



Related Topics



Leave a reply



Submit