- 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
end
Legacy 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])
end
After 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)
end
LOL? 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)
end
Now 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