- Rails 5+
- Rspec
I don't wanna be stuck with it again.
Ok, maybe this is trivial, but maybe that'll help someone:
ApiController:
def approve_comment
if comment.update("admin_approval": 'approved')
render json: comment
else
not_approved_method #do something
render json: comment.errors, status: :unprocessable_entity
end
endLegacy code in spec to refactor:
describe 'PATCH approve_comment' do
let(:comment_to_approve) { create :comment, :pending }
let(:action) { patch :approve_comment, params: { id: comment_to_approve.id } }
...
context 'when approve returns false' do
before do
allow_any_instance_of(Comment).to receive(:update).and_return(false)
end
it 'returns errors' do
action
expect(response).to have_http_status :unprocessable_entity
end
it 'do sth more e.g. warn the admins' do
action
# expect something more
end
end
end
Using allow_any_instance_of() force update to return false for all instances of Comment (so for
for any comment object) therefore does it make possible to test private method not_approved_method.
At first
before do
allow(comment_to_approve).to receive(:update).and_return(false)
end^ Of course doesn't work => different object in spec and controller.
Stub with instance_double(Model) / allow(Model) doesn't work too.
Whatever I tried I receive: expected the response to have status code :unprocessable_entity (422) but it was :ok (200)
I don't need it. The main problem was with objects.
After all, even if my comment in controller has same id from db as comment_to_approve in spec, it was a different object.
Why?
Because I missed a method in Controller that searches a comment to approve:
def comment
@comment ||= Comment.find(params[:id])
endAfter find comment looked the same, but it wasn't same object.
Partial fix:
before
allow_any_instance_of(described_class).to receive(:comment).and_return(comment_to_approve)
allow(comment_to_approve).to receive(:update).and_return(false)
endLOL? How is this supposed to be called 'the fix' ... I still see allow_any_instance_of!
Let me explain.
Now, we stub that descriped class always exacly the same object.
So comment method will not use find to return new object but with same ID.
This directs us to a good solution, which looks like:
before do
allow(Comment).to receive(:find).and_return(comment_to_approve)
allow(comment_to_approve).to receive(:update).and_return(false)
endNow the whole stuff looks like that:
describe 'PATCH approve_comment' do
let(:comment_to_approve) { create :comment, :pending }
let(:action) { patch :approve_comment, params: { id: comment_to_approve.id } }
...
context 'when approve returns false' do
before do
allow(Comment).to receive(:find).and_return(comment_to_approve)
allow(comment_to_approve).to receive(:update).and_return(false)
end
it 'returns errors' do
action
expect(response).to have_http_status :unprocessable_entity
end
it 'do sth more e.g. warn the admins' do
action
# expect something more
end
end
end