How to write a good request spec for #index? - ruby-on-rails

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)

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 assert url was called from javascript using rspec and capybara

Scenario:
We use capybara integration tests to test that our frontend plumbing (javascript) is connected properly.
Sometimes all we need to validate the test is:
has content rendered properly on the page
has the js called the correct url open interaction
Problem:
Item 1 above is easy. However, with item 2 I can't seem to find an easy way to say:
Assert that url was called from js in browser.
Example:
it "should call coorect url with correct query string" do
visit widgets_path
# THIS IS WHAT I NEED TO KNOW
expect(something).to receive(:some_method).with(url: "my/test/url", params: {per_page: 2})
# In other words, I don't want the controller action to run. I don't care about the result since the controller is being tested elsewhere.
# I just need to know that the correct URL was called with the correct params.
within 'ul.pagination' do
click_on '2'
end
end
I've tried mocking the controller action, but there's no way to inspect the params. Or is there? Without inspecting the params, how can I know if the correct stuff was sent? All I know it's the correct route, which isn't enough.
If I could inspect the params then this would be solved... but otherwise?
If you are looking for the Rails solution, here it is! Tested with Rails 5.1.3.
1) Create a request params matcher spec/support/matchers/request_with_params.rb
RSpec::Matchers.define :request_with_params do |params|
match { |request| request.params.symbolize_keys.slice(*params.keys) == params }
end
2) Create a helper method for your acceptance tests (you can use some logics to pass symbol instead of class UsersController -> :users if needed)
def expect_request(controller, action, params = {})
expect_any_instance_of(ActionDispatch::Routing::RouteSet::Dispatcher)
.to receive(:dispatch)
.with(controller, action.to_s, request_with_params(params), anything)
end
end
3) Use it!
expect_request(UsersController, :index)
or with params
expect_request(UsersController, :show, { id: 1 })
OR
4) There is another way in using https://github.com/oesmith/puffing-billy Check this gem for intercepting requests sent by your browser. But it can be an overkill if you need to mock only certain requests to your backend app.
Capybara integration tests intentionally don't support that. They are end-to-end blackbox tests, shouldn't generally be mocked, and really only support checking for things visible to the user in the browser. In your example case that would mean expecting on whatever visible change is caused by the JS call to the specific URL. Something like
expect(page).to have_css('div.widget', count: 2)

Rails: For API's what should I write unit/functional/integration test cases?

I have rails microservices application for which I would like to write test cases, I would like to have suggestions
For API what are the possible type test cases available?
what type of test cases should I write functional/unit/integration?
What is the difference in functional/unit/integration if we talk about it in the context of API's?
Note: My application is having features like chatting, booking, payments
Well, first of all. You should understand that API it's just a controller. So you need just to check that your api's action do proper thing (crud or any other thing) and return proper fields. Much better to move this 'thing' to some command (this is a pattern), for example gem like this.
In this case your tests will be easier for support/maintain. Because in 'controller' spec you will just check what do you have in response. And for 'proper action' (for example creating of record) will respond your command.
So in the end you will have test for commands and controllers. Your controller spec will just check presence of values that is returned by serializers (AMS for example).
With commands all your controllers will look like:
def action
data = SomeImportantCommand.new(param1: params[:user], param2: param[:form]).call
respond_with data, serializer: Api::V1::SomeEpicSerializer
end
This is pseudo code, but it shows idea of command usage.
Such approach is more complicated, but it has advantages.
You are using commands that can be tested separately from controllers (here you have all your business logic).
Difficult logic can be splitted for few commands.
Because of 2-nd list item you will have simply controller test, that easy to maintain. And you can be absolutely sure that front-end application/developer will recieve all necessary data.
All your specs for controller will look like:
it 'returns some json' do
get '/api/v1/users'
expect(response.status).to eq 200
expect(response.body).to have_node(:name).with('Ivan')
# check other fields if you want
end
In code above api_matchers gem is used to parse json response.
p.s. Also you need tests for models, but this is ordinary thing, nothing special for API.

What's a good way to stub Contentful Model instances in feature specs?

(I think this question generalises to stubbing any extensively-pinged API, but I'm asking the question based on the code I'm actually working with)
We're using the Contentful Model extensively in our controllers and views including in our layouts. This means that in any feature test where we visit (say) the homepage, our controller action will include something like this:
class HomepageController < ApplicationController
def homepage
# ... other stuff
#homepage_content = Homepage.find ('contentful_entry_id')
end
end
... where Homepage is a subclass of ContentfulModel::Base, and #homepage_content will have various calls on it in the view (sometimes chained). In the footer there's a similar instance variable set and used repeatedly.
So for feature testing this is a pain. I've only come up with two options:
Stub every single call (dozens) on all Contentful model instances, and either stub method chains or ensure they return a suitable mock
or
Use a gem like VCR to store the Contentful responses for every feature spec
Both of these (at least the way I'm doing them) have pretty bad drawbacks:
1) leads to a bunch of test kruft that will have to be updated every time we add or remove a field from the relevant model;
2) means we generate a vcr yaml files for every feature test - and that we have to remember to clear the relevant yml file whenever we change an element of the test that would change the requests it sends
Am I missing a third option? Or is there some sensible way to do either of the above options without getting the main drawbacks?
I'm the maintainer of contentful_model.
We use VCR to stub API Calls, so that you can test with real data and avoid complicated test code.
Cheers

Dynamic method call in routes spec

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"

Resources