'How to properly mock itnernal services in RSpec?
I would like to learn how to properly mock object calls inside other classes, foor example I have this controller action:
def show
service = Action::PartsShow.new(show_params, current_user)
service.call
render json: service.part, root: :part, serializer: PartSerializer, include: '**',
scope: {current_user: current_user}
end
The service class looks like this.
module Action
class PartsShow < PartsShowBase
def find_part
...
end
end
end
module Action
class PartsShowBase
attr_reader :part
def initialize(params, current_user)
@params = params
@current_user = current_user
end
def call
find_part
reload_part_availability
reload_part_price if @current_user.present?
end
private
def reload_part_availability
ReloadPartAvailabilityWorker.perform_async(part.id)
end
def reload_part_price
ExternalData::LauberApi::UpdatePrices.new(@current_user, [part]).execute
end
end
end
I don't want to call the actual Action::PartsShow service inside this controller action and all other methods, services + the worker because this makes the test very slow. What I want is to test if this service is being called and mock the rest of the services. I don't want to call them in my tests, I want to mock them.
My test looks like this:
RSpec.describe PartController, type: :request do
describe 'GET #show' do
let(:part) { create(:part) }
subject { get "/api/v1/parts/#{part.id}" }
expect(response_body).to eq(200)
# ...
end
end
Could you show me how to properly mock it? I've read about RSpec mocks and stubs but I am confused about it. I would appreciate your help.
Solution 1:[1]
Assuming that find_part calls Part.find(id), you can add:
before do
allow(Part).to receive(:find).with(part.id).and_return(part)
end
to ensure that the record lookup always returns your test object. I also suggest reworking your spec a bit:
RSpec.describe PartController, type: :request do
subject { response }
describe '#show' do
let(:request) { get api_v1_part_path(part.id) }
# If you insist on mocking the object, might as well use build_stubbed
let(:part) { build_stubbed(:part) }
let(:json) { JSON.parse(response.body).deep_symbolize_keys }
let(:expected) {
{
part: {
id: parts.id,
# ...
}
}
}
before do
# This is the recommended way to mock an object
allow(Part).to receive(:find).with(part.id).and_return(part)
request
end
# Validate response status
# https://relishapp.com/rspec/rspec-rails/docs/matchers/have-http-status-matcher
# If you are using shoulda matchers - it works bc subject is the response
it { should have_http_status(:success) }
# otherwise
it { expect(response).to have_http_status(:success) }
# Validate response body
it { expect(json).to eq(expected) }
end
end
If your project has path helpers, I would also recommend using those instead of the path string.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
