Reusing minitest integration tests - ruby-on-rails

I have three pages in Rails that all display the same header, and hence would require the exact same integration tests.
Instead of repeating myself and writing separate tests that look almost exactly the same, what's the best approach here? I've tried putting the shared assertions into a module but haven't been successful getting it to load into each test scenario.
UNDRY:
class IntegrationTest
describe "page one" do
before { visit page_one_path }
it "should have a home page link" do
page.find_link "Home"
end
end
describe "page two" do
before { visit page_two_path }
it "should have a home page link" do
page.find_link "Home"
end
end
describe "page three" do
before { visit page_three_path }
it "should have a home page link" do
page.find_link "Home"
end
end
end
Failed attempt at drying it out...
Module:
/lib/tests/shared_test.rb
module SharedTest
def test_header
it "should have a home page link" do
page.find_link "Home"
end
end
end
Test:
class IntegrationTest
include SharedTest
describe "page one" do
before { visit page_one_path }
test_header
end
describe "page two" do
before { visit page_two_path }
test_header
end
describe "page three" do
before { visit page_three_path }
test_header
end
end
I haven't quite figured out how to write modules yet so it's no surprise that this doesn't work. Can someone point me in the right direction?

The way to share tests between different describe blocks when using Minitest's spec DSL is to include the module in each describe block you want those tests to run in.
module SharedTest
def test_header
assert_link "Home"
end
end
class IntegrationTest < ActiveDispatch::IntegrationTest
describe "page one" do
include SharedTest
before { visit page_one_path }
end
describe "page two" do
include SharedTest
before { visit page_two_path }
end
describe "page three" do
include SharedTest
before { visit page_three_path }
end
end
One of the ways the Minitest's Test API is different than Minitest Spec DSL is in how they behave when being inherited. Consider the following:
class PageOneTest < ActiveDispatch::IntegrationTest
def setup
visit page_one_path
end
def test_header
assert_link "Home"
end
end
class PageTwoTest < PageOneTest
def setup
visit page_two_path
end
end
class PageThreeTest < PageOneTest
def setup
visit page_three_path
end
end
The PageTwoTest and PageThreeTest test classes inherit from PageOneTest, and because of that they all have the test_header method. Minitest will run all three tests. But, when implemented with the spec DSL the test_header method is not inherited.
class PageOneTest < ActiveDispatch::IntegrationTest
def setup
visit page_one_path
end
def test_header
assert_link "Home"
end
describe "page two" do
before { visit page_two_path }
end
describe "page three" do
before { visit page_three_path }
end
end
In this case, only one test is run. The test class created by describe "page two" will inherit from PageOneTest, but will have all of the test methods removed. Why? Because Minitest's spec DSL is based on RSpec, and this is the way that RSpec works. Minitest goes out of its way to nuke the test methods that are inherited when using the spec DSL. So the only way to share tests while using the spec DSL is to include the module in each describe block you want them to be in. All other non-test methods, including the before and after hooks, and the let accessors, will be inherited

Here's a clean way to reuse tests when using MiniTest's spec DSL - define the tests inside a function and call that function where you want to include your tests.
Example:
def include_shared_header_tests
it "should be longer than 5 characters" do
assert subject.length > 5
end
it "should have at least one capital letter" do
assert /[A-Z]/ =~ subject
end
end
# This block will pass
describe "A good header" end
subject { "I'm a great header!" }
include_shared_header_tests
end
# This block will fail
describe "A bad header" end
subject { "bad" }
include_shared_header_tests
end

If you want to continue to use the Spec style in the module, you can use the Module::included hook.
module SharedTests
def self.included spec
spec.class_eval do
# Your shared code here...
let(:foo) { 'foo' }
describe '#foo' do
it { foo.must_equal 'foo' }
end
end
end
end
class MyTest < Minitest::Spec
include SharedTests
end
class MyOtherTest < Minitest::Spec
include SharedTests
end
# It also works in nested describe blocks.
class YetAnotherTest < Minitest::Spec
describe 'something' do
describe 'when it acts some way' do
include SharedTests
end
end
end
Another approach to repetitive tests is iteration.
class IntegrationTest < Minitest::Spec
{
'page one' => :page_one_path,
'page two' => :page_two_path,
'page three' => :page_three_path,
}.each do |title, path|
describe title do
before { visit send(path) }
it 'should have a home page link' do
page.find_link 'Home'
end
end
end
end

Related

Testing will paginate gem with Rspec/Capybara

In my application there is an admin part, which is restricted to superadmins (users with a property superadmin: true). I've got a shop list, which I want to get paginated and tested.
When debugging the current code with save_and_open_page I get a blank page. If I log in not as a superadmin, I get redirected to application's root and when trying to debug with save_and_open_page is see the root page.. If I do not log in at all, then I'll get redirected to the sign in page. So the basic functionality should work.
I'm having no clue why it does not work with superadmin and why I do not see the shops list when debugging with save_and_open_page.
This is my spec/controllers/shops_controller_spec.rb (copied basically from here) :
require 'rails_helper'
RSpec.describe Admin::ShopsController, type: :controller do
context "GET methods" do
describe "#index action" do
before(:all) {
amount = Rails.application.config.page_size
amount.times { FactoryGirl.create(:shop) }
}
before(:each) {
login_as(FactoryGirl.create(:user, superadmin: true), :scope => :user)
}
context "with entries == config.page_size" do
it "has no second page" do
get :index
expect(response).not_to have_selector("a", :href => "/shops?page=2", :content => "2")
# visit admin_shops_path
# expect(page).to have_no_xpath("//*[#class='pagination']//a[text()='2']")
end
end
context "with entries > config.page_size" do
before { FactoryGirl.create(:shop) }
it "has a second page with too many entries" do
visit "/admin/shops"
save_and_open_page
expect(page).to have_xpath("//*[#class='pagination']//a[text()='2']")
end
it "correctly redirects to next page" do
visit admin_shops_path
find("//*[#class='pagination']//a[text()='2']").click
expect(page.status_code).to eq(200)
end
end
end
end
end
As you can see, I tried to test in different ways (the "expect block" is taken from this SO-question), but none of them work. Using get :index I receive
Admin::ShopsController GET methods #index action with entries == config.page_size has no second page
Failure/Error: expect(page).not_to have_selector("a", :href => "/shops?page=2", :content => "2")
ArgumentError:
invalid keys :href, :content, should be one of :count, :minimum, :maximum, :between, :text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set
Here is my AdminController.rb if it helps:
class AdminController < ApplicationController
layout 'admin'
before_action :authenticate_user!, :verify_is_superadmin
before_action :set_locale
before_action :get_breadcrumbs
private
def get_breadcrumbs
splitted_url = request.original_fullpath.split("/")
# Remove first object
splitted_url.shift
result = splitted_url.map { |element| element.humanize.capitalize }
session[:breadcrumbs] = result
# debug
end
def set_locale
I18n.locale = params[:locale] || session[:locale] || I18n.default_locale
# session[:locale] = I18n.locale
end
def verify_is_superadmin
(current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.superadmin?)
end
end
Update
Using Thomas' answer I ended up putting my code in spec/features and it looks like this right now:
require "rails_helper"
RSpec.feature "Widget management", :type => :feature do
before(:each) {
amount = Rails.application.config.page_size
amount.times { FactoryGirl.create(:shop) }
}
before(:each) {
login_as(FactoryGirl.create(:user, superadmin: true), :scope => :user)
}
scenario "with entries == config.page_size" do
visit admin_shops_path
#save_and_open_page
expect(page).to have_no_xpath("//*[#class='pagination']//a[text()='2']")
end
scenario "with entries > config.page_size" do
FactoryGirl.create(:shop)
visit admin_shops_path
expect(page).to have_xpath("//*[#class='pagination']//a[text()='2']")
end
scenario "with entries > config.page_size it correctly redirects to next page" do
FactoryGirl.create(:shop)
visit admin_shops_path
find("//*[#class='pagination']//a[text()='2']").click
expect(page.status_code).to eq(200)
end
end
Everything works!
You've got a number of issues here.
Firstly the other SO question you linked to isn't using Capybara so copying its examples for matchers is wrong.
Secondly you are writing controller tests, not view tests or feature tests. controller tests don't render the page by default, so to test elements on the page you want to be writing either view tests or feature tests. Capybara is designed for feature tests and isn't designed for controller tests. This is why the default capybara/rspec configuration file only includes the Capybara DSL into tests of type 'feature'. It also includes the Capybara RSpec matchers into view tests since they are useful with the rendered strings provided there.
Thirdly, you are mixing usage of get/response, and visit/page in the same file which just confuses things.
If you rewrite these as feature tests, then to check you don't have a link with a specific href in capybara you would do
expect(page).not_to have_link(href: '...')
If you want to make sure that a link doesn't exist with specific text and a specific href
expect(page).not_to have_link('link text', href: '...')
Note: that checks there is not a link with both the given text and the given href, there could still be links with the text or the href

Why i can not get current_user while writing test case with Rspec and Capybara

I have to write integration test case for my one feature listing page and that feature index method has code like below
def index
#food_categories = current_user.food_categories
end
Now when i try to write a test case for this it throws an error
'undefined method features for nil class' because it can not get the current user
Now what i have do is below
I have write the login process in the before each statement and then write the test case for the features listing page
Can you please let me know that how i can get the current_user ?
FYI, I have used devise gem and working on integration test case with Rspec
Here is my spec file
And here is my food_categories_spec.rb
Update: you confuse functional and integration tests. Integration test doesn't use get, because there's no controller action to test, instead you must use visit (some url). Then you have to examine content of a page, not response code (latter is for functional tests). It may look like:
visit '/food_categories'
page.should have_content 'Eggs'
page.should have_content 'Fats and oils'
In case you'll need functional test, here's an example:
# spec/controllers/your_controller_spec.rb
describe YourController do
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
describe "GET index" do
before do
get :index
end
it "is successful" do
response.should be_success
end
it "assings user features" do
assigns(:features).should == #user.features
end
end
end
# spec/spec_helper.rb
RSpec.configure do |config|
#...
config.include Devise::TestHelpers, :type => :controller
end

How do you "nest" or "group" Test::Unit tests?

RSpec has:
describe "the user" do
before(:each) do
#user = Factory :user
end
it "should have access" do
#user.should ...
end
end
How would you group tests like that with Test::Unit? For example, in my controller test, I want to test the controller when a user is signed in and when nobody is signed in.
You can achieve something similar through classes. Probably someone will say this is horrible but it does allow you to separate tests within one file:
class MySuperTest < ActiveSupport::TestCase
test "something general" do
assert true
end
class MyMethodTests < ActiveSupport::TestCase
setup do
#variable = something
end
test "my method" do
assert object.my_method
end
end
end
Test::Unit, to my knowledge, does not support test contexts. However, the gem contest adds support for context blocks.
Shoulda https://github.com/thoughtbot/shoulda although it looks like they've now made the context-related code into a separate gem: https://github.com/thoughtbot/shoulda-context
Using shoulda-context:
In your Gemfile:
gem "shoulda-context"
And in your test files you can do things like (notice the should instead of test:
class UsersControllerTest < ActionDispatch::IntegrationTest
context 'Logged out user' do
should "get current user" do
get api_current_user_url
assert_response :success
assert_equal response.body, "{}"
end
end
end

Rails 3 how to reset controller before_filters between specs?

I'm writing specs for a plugin which has different modules that the user can choose to load.
Some of these modules dynamically add before_filters to ApplicationController.
The problem is sometimes if the spec for module X runs and adds a before_filter, the spec for module Y which runs later will fail. I need somehow to run the second spec on a clean ApplicationController.
Is there a way to remove before filters or reload ApplicationController completely between specs?
For example in the following specs, the second 'it' does not pass:
describe ApplicationController do
context "with bf" do
before(:all) do
ApplicationController.class_eval do
before_filter :bf
def bf
#text = "hi"
end
def index
#text ||= ""
#text += " world!"
render :text => #text
end
end
end
it "should do" do
get :index
response.body.should == "hi world!"
end
end
context "without bf" do
it "should do" do
get :index
response.body.should == " world!"
end
end
end
You should be able to do this using context blocks to separate the two sets of examples.
describe Something do
context "with module X" do
before(:each) do
use_before_fitler
end
it_does_something
it_does_something_else
end
context "without module X" do
it_does_this
it_does_that
end
end
The before_filter should only affect the examples in the "with module X" context.
I'd use separate specs on subclasses rather than ApplicationController itself:
# spec_helper.rb
def setup_index_action
ApplicationController.class_eval do
def index
#text ||= ""
#text += " world!"
render :text => #text
end
end
end
def setup_before_filter
ApplicationController.class_eval do
before_filter :bf
def bf
#text = "hi"
end
end
end
# spec/controllers/foo_controller_spec.rb
require 'spec_helper'
describe FooController do
context "with bf" do
before(:all) do
setup_index_action
setup_before_filter
end
it "should do" do
get :index
response.body.should == "hi world!"
end
end
end
# spec/controllers/bar_controller_spec.rb
require 'spec_helper'
describe BarController do
before(:all) do
setup_index_action
end
context "without bf" do
it "should do" do
get :index
response.body.should == " world!"
end
end
end

Dynamically test each method in a controller

I have an Ruby on Rails 3 admin_controller with the default set of CRUD, index and so on methods. I'd like to test each of these for certain assertions with rspec.
Like response.should render_template("layouts/some_layout") or tests that it should require login.
Copy-pasting that test into the group of tests for each method is a lot of duplication. IMO it makes little sense to have an
it 'should require login' do
Duplicated several times troughout that test.
Is there a simple way to run a test on a list of methods? Say defined_methods.each do |method| it 'should' .... of some sort?
Is this a good way in the first place? Or am I taking a wrong route in the first place?
Given that you really want all those assertions, have you considered shared example groups?
shared_examples_for "an action that requires authentication" do
it "should render successfuly" do
sign_in(user)
response.should be_success # or whatever
end
it "should deny access" do
# don't sign_in the user
# assert access was denied
end
end
shared_examples_for "another behaviour" do
# ...
end
let(:user) { create_user }
describe "#index" do
before(:each) { get :index }
it_behaves_like "an action that requires authentication"
it_behaves_like "another behaviour"
end
describe "#show" do
before(:each) { get :show }
it_behaves_like "an action that requires authentication"
end
# ...
Of course before writing large number of specs for a basic functionality you should always check if it isn't already tested by the library that is providing the functionality (e.g. checking for the rendered template, if it is handled by rails's implicit rendering, might be a bit overkill).
If you wanted to go down the route of iteratively testing each public method in the controller, you could do something like:
SomeController.public_instance_methods(false).each do |method|
it "should do something"
end
However, I think a shared example group (see about half way down this page: http://rspec.info/documentation/) would be prettier. If it were extracted so it could be used across all your controller specs, it'll be even nicer..
shared_examples_for "admin actions" do
it "should require login"
end
Then in each controller spec:
describe SomeController do
it_should_behave_like "admin actions"
end
Just add it to your test_helper.rb, something like:
def requires_login
...
end

Resources