Dynamic method call in routes spec - ruby-on-rails

I am testing simple get requests for my routes using rspec in my Rails 3.2 application. Since all are get requests, and all just have different action names which are similar to the views' names, it would be really repetitive to manually write a different test for each get request.
Instead, I wanted to come up with something like this:
%(action_1 action_2 action_3 action_4).each do |action|
it "routes to the #{action} page" do
get("liver_diseases#{action}_path").should route_to("liver_diseases##{action}")
end
end
It fails at this pseudocode: get("liver_diseases_#{action}_path")
So what I need to do is a dynamic method call - but for what I have found out, that would involve .send(:method_name), for which I need to know the class name. And I couldn't find that.
What do I need to do for this method call to work?

that would involve .send(:method_name), for which I need to know the
class name
When the receiver is missing, it's always self. In the context of a controller example, self should be a controller instance. So you should be able to get that path with:
send "liver_diseases_#{action}_path"
which should be equivalent to:
controller.send "liver_diseases_#{action}_path"

Related

Rspec: Test if instance method was called on specific record with ActiveRecord

I'm looking to write a controller spec which tests if a method was called on an instance of a model class for ActiveRecord.
For example, there is a model Post and I want to check if the post with the id 55 had the method foobar called on it.
Some ways that almost work:
expect_any_instance_of(Post).to receive(:foobar)
This almost works but it can not check which post the method was called on
using double
This would normally work but in the controller spec, only ids are passed over so I have no way of inserting the double, short of mocking the response from activerecord find
Does rspec provide any tools to check a method was called on a specific model instance?
It is possible but requires much more mocking, and this means that you're testing the implementation details of a controller which is an antipattern.
Assuming your controller is this:
def action(id)
Post.find(params[:id]).foobar
end
You'd need to mock sth like this (I'll ignore RSpec's good practices like having vars in let's, you can easily extract it later when you have a working example):
mock = instance_double(Post)
expect(Post).to receive(:find).with(55).and_return(mock)
expect(mock).to receive(:foobar)
# trigger the action here
But as the comments already stated, it's probably cleaner to test side effects. If your action deletes a Post, check that the post has been deleted
expect { trigger_action params: {id: 55} }.to change(Post, :count).by(-1)
This makes your code less brittle (you can refactor the internals, and the specs for the controller stay green - meaning your app still works as described in those specs).

How to write a good request spec for #index?

I am new to TDD and really enjoy it. I am using RSpec.
I am trying to learn to write good request specs (in general) and can find very little written on how to test the index method.
I have found this article: https://medium.com/#lcriswell/rails-api-request-specs-with-rspec-effeac468c4e, but I am not interested in testing an API, but an application with views.
What should I include on my index request tests and why?
The first spec in the article is great, you can use it in testing a regular controller for responses.
If you are using any sort of authorization(e.g cancancan), you can test the same request for multiple types of users and check if you get a redirect or a success(you might have to mock the sign-in).
For testing the views that are being rendered, you can try this:
it { is_expected.to render_template(:index) }
If your action assigns instance variables, you can test out that the variable is a certain value like so:
expect(assigns(:foo)).to be true
If your action responds to different formats(HTML, json, ...), you can write different contexts for each of the formats, each time by changing the request(hint: for JS in your specs submit your request like so: get :index, xhr: true)

Rspec: How to use expect to receive with a resource which does not exist yet?

In an action called via a post request I'm creating a resource call RequestOffer and send an email with ActionMailer using the created resource as a parameter:
#request_offer = RequestOffer.new(request_offer_params)
if #request_offer.save
RequestOfferMailer.email_team(#request_offer).deliver_later
end
When my controller spec, I want to test that my RequestOfferMailer is called using the method email_team with the resource #request_offer as a parameter.
When I want to user expect(XXX).to receive(YYY).with(ZZZ), the only way I found was to declare my expectation before making the POST request. However, ZZZ is created by this POST request, so I have no way to set my expectation before.
# Set expectation first
message_delivery = instance_double(ActionMailer::MessageDelivery)
# ZZZ used in .with() does not exist yet, so it won't work
expect(RequestOfferMailer).to receive(:email_team).with(ZZZ).and_return(message_delivery)
expect(message_delivery).to receive(:deliver_later)
# Make POST request that will create ZZZ
post :create, params
Any idea how to solve this problem?
If this is a functional test then I would isolate the controller test from the DB. You can do this by using instance_doubles and let statements. Here's an example that you may like to extend for your purposes
describe '/request_offers [POST]' do
let(:request_offer) { instance_double(RequestOffer, save: true) }
before do
allow(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
end
it 'should instantiate a RequestOffer with the params' do
expect(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
post '/request_offers', {...}
end
it 'should email the request offer via RequestOfferMailer' do
mailer = instance_double(ActionMailer::MessageDelivery)
expect(RequestOfferMailer).to receive(:email_team).
with(request_offer).and_return(mailer)
post '/request_offers', {...}
end
end
The key to this is using 'let' to declare an instance double of the model that you intend to create. By setting expectations on the class you can inject your instance double into the test and isolate from the DB. Note that the 'allow' call in the before block is there to serve the later specs that set expectations on the mailer object; the 'expect' call in the first test will still be able to make assertions about the call.
Would it be enough to make sure the argument is an instance of RequestOffer? Then you could use the instance_of matcher. For example:
expect(RequestOfferMailer).to receive(:email_team).with(instance_of(RequestOffer)).and_return(message_delivery)
I found this option in the Rspec 3.0 docs: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/setting-constraints/matching-arguments
The last argument of the with method is a block. You can open up the arguments and do anything you like there.
expect(RequestOfferMailer)
.to receive(:email_team)
.with(instance_of(RequestOffer)) do |request_offer|
expect(request_offer.total).to eq(100) # As one example of what you can to in this block
end.and_return(message_delivery)
You can also set the instance_of matcher to be anything if you're not even sure what object type you're expecting.

How to Test an Instance Variable in Rspec

I am having trouble testing an external instance variable with Rspec. While there are some similar questions on StackOverflow, I haven't been able to find a solution for testing it in a GET request (since this is for an API).
I have an ApiController method that does something along these lines:
class ApiController < ApplicationController
...
def auth(token)
#user = User.find_by_token(token)
...
end
Then, I have a request spec that looks something like this:
describe 'GET /v1/friends' do
it 'returns friends' do
ApiController.any_instance.stub(:auth)
get "v1/friends"
...
end
end
I get an error that says undefined method XXX for nil:NilClass that stems from the fact that the #users instance variable isn't being passed into the GET request.
I have tried using assigns() and that doesn't seem to work, while not stubbing the auth class isn't an option for a different reason. And, simply setting the instance variable doesn't seem to work, because I've read that Rspec assumes that it's its own variable rather than one used in the test (?).
Is there any way to set the #user instance variable to a fake user when running the GET request?
Thanks in advance for any help!
I ended up refactoring quite a bit of other code to run the authentication method that creates the instance variable in the actual controller. I don't believe there is a way to set a variable in Rspec for use in a GET request.
When using an integration test to test authenticate_with_http_token, you'll need to include it like so in the GET request:
get '/v1/path-for-api', nil, authorize: 'Token token=#{user.token}'
From there, the variable was properly set now and I was able to test the requests.

How do I determine the default action for a Rails controller?

I'm working on a functional test that needs to assert that a certain XHTML tag is present in a certain set of controllers. I'm basically doing this:
class ApplicationControllerTest < ActionController::TestCase
def setup
#controller = ApplicationController.new
end
# [...]
def test_foo_bar_and_baz_include_stylesheet
[FooController, BarController, BazController].each do |controller|
#controller = controller.new
get :show
assert_select 'head > link[rel="stylesheet"]'
end
end
end
The problem is that not all controllers have a :show action. What I need to do is ask either the controller or the routing configuration for the controller's default action and call get with that.
Update: Joseph Silvashy was right: I ended up separating my controller gets out into separate test cases. Solving the "default" route problem was bad enough, until I discovered that some of my routes had conditions attached, and I would have to parse them to craft the correct get call. And finally, Rails functional tests don't deal very well with calling get multiple times in the same test case, especially when that those calls are hitting multiple controllers. :(
I think the lesson here is one that we all know by heart but sometimes is hard to accept: if the code looks hairy, you're probably doing it wrong. ;)
Controllers don't have a "default" view or action, additionally actions can be named anything you want, they don't have to be the standard index, show, new, etc...
I'll probably have to :get the appropriate action for each controller you want to test. It's likely each test will be different down the road anyhow, even though right now they all have the same requirement, I think it makes sense to write one for each action regardless.
try to use the respond_to function: http://www.ruby-doc.org/core/classes/Object.html#M001005
to check if a method exitst.

Resources