context "answer is correct" do
before(:each) do
#answer = stub_model(Answer, :correct => true).as_new_record
assigns[:answer] = #answer
render "answers/summarize"
end
it "should display flashcard context properly" do
response.should contain("Quiz")
end
it "should summarize results" do
response.should contain("is correct")
end
end
context "answer is incorrect" do
before(:each) do
#answer = stub_model(Answer, :correct => false).as_new_record
assigns[:answer] = #answer
render "answers/summarize"
end
it "should display flashcard context properly" do
response.should contain("Quiz")
end
it "should summarize results" do
response.should contain("is incorrect")
end
end
How do I avoid repeating the following block within both of the above contexts?
it "should display flashcard context properly" do
response.should contain("Quiz")
end
Your specs describe the behavior you expect from your code - this amount of repetition is ok.
If it gets out of hand, use different contexts to isolate different expectations. For example, you could factor those duplicate expectations into a new context that just tests the page no matter what the Answer is.
If you really want to wrap up some of your tests, you can do it like this:
def answer_tests
it "should display flashcard context properly" do
response.should contain "Quiz"
end
it "should do be awesome" do
response.should be_awesome
end
end
context "answer is correct" do
answer_tests
it "should summarize results" do
response.should contain "is correct"
end
end
context "answer is incorrect" do
answer_tests
it "should summarize results" do
response.should contain "is incorrect"
end
end
As you can see, this is really handy when you have more than one test you want to wrap up in a method.
Related
My application manages BusinessFlows, which can only be created within a parent BusinessArea. The 'new' method in BusinessFlows controller is:
def new
#business_area = BusinessArea.find(params[:business_area_id])
#business_flow = BusinessFlow.new
end
The Factory used for testing works fine, and creates the parent BusinessArea, and the the BusinessFlow as expected:
FactoryBot.define do
factory :business_flow do
association :parent, factory: :business_area
playground_id {0}
name {"Test Business Flow"}
code {Faker::Code.unique.rut}
description {"This is a test Business Flow used for unit testing"}
created_by {"Fred"}
updated_by {"Fred"}
owner_id {0}
responsible_id {0}
deputy_id {0}
organisation_id {0}
status_id {0}
end
end
But when it comes to test if the 'new' view can be displayed, the controller raises an error due to missing params[:business_area_id]:
ActiveRecord::RecordNotFound:
Couldn't find BusinessArea without an ID
Here is the feature test script:
require 'rails_helper'
RSpec.describe BusinessFlow, type: :request do
include Warden::Test::Helpers
describe "Business Flows pages: " do
let(:bf) {FactoryBot.create(:business_flow)}
context "when not signed in " do
it "should propose to log in when requesting index view" do
get business_flows_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting new view" do
get new_business_flow_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting edit view" do
get edit_business_flow_path(bf)
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show view" do
get business_flow_path(bf)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_flows_path
expect(response).to render_template(:index)
end
it "should display new view" do
get new_business_flow_path(bf.parent.id)
expect(response).to render_template(:_form)
end
it "should display edit view" do
get edit_business_flow_path(bf)
expect(response).to render_template(:_form)
end
it "should display show view" do
get business_flow_path(bf)
expect(response).to render_template(:show)
end
end
end
end
Only the 'new' method test in the context 'when signed in' fails.
Can you please help me solving this?
Thanks a lot!
it "should display new view" do
get new_business_flow_path(business_area_id: bf.parent)
expect(response).to render_template(:_form)
end
Rails thinks the id you're passing is part of the business flow path (and it isn't), it needs to be passed as a param I think.
I have a spec like this:
context 'index' do
let!(:article) { create :article }
subject { visit articles_path }
specify do
subject
expect(page).to have_content(article.title)
end
end
However when I try to refactor it like this, it says I have 0 examples:
context 'index' do
let!(:article) { create :article }
subject { visit articles_path }
context do
after { expect(page).to have_content(article.title) }
end
context do
before { login_as :user }
after { expect(page).to have_content(article.comment) }
end
context do
after {}
end
end
Shouldn't it be running subject and then the after hook? I am pretty sure I have used this setup before.
Please tell me how to correctly refactor this. Thanks.
Update
Could do it like this, but I don't really like it:
subject do
visit articles_path
page
end
specify do
expect(subject).to have_content(article.title)
end
'after' block is normally used to do something after the test example has been executed. In your second example you have no test executed prior to the 'after' block
I would refactor your code like this
context 'index' do
let!(:article) { create :article }
visit articles_path
expect(page).to_not have_content(category.title)
end
edit
context 'index' do
let!(:article) { create :article }
before do
visit articles_path
end
context do
it "displays article's title" do
expect(page).to have_content(article.title)
end
end
context do
before { login_as :user }
it "displays article's comments" do
expect(page).to have_content(article.comment)
end
end
context do
#another test
end
end
It seems to me from you refactoring attempt that there's a bit of confusion.
First, the reason why you were seeing 0 examples, 0 failures is because there is no subject invocation in your tests. Think about it like this:
subject { visit articles_path }
it 'has a nice title' do
subject
expect(page).to have_content(article.title)
end
it 'has a nice comment' do
subject
expect(page).to have_content(article.comment)
end
In order for your expectations to work you need to call the subject. In fact you could have even avoided using a subject by explicitly writing in your it/specify blocks visit articles_path
it 'has a nice title' do
visit articles_path
expect(page).to have_content(article.title)
end
it 'has a nice comment' do
visit articles_path
expect(page).to have_content(article.comment)
end
Tests that share the same subject can be dried up using subject { ... }.
Second, don't confuse context blocks with specify/it blocks(remember they're aliased).
A context is a way to make your tests more understandable by separating different results for the test.
subject { visit articles_path }
context 'user is logged in' do
it 'displays article's title' do
login_as :user
subject
expect(page).to have_content(article.title)
end
it 'displays article's title' do
login_as :user
subject
expect(page).to have_content(article.comment)
end
end
context 'user not logged in' do
it 'displays article's comments' do
subject
expect(page).to have_content('Log in')
end
end
You can have different expectations, it/specify blocks in the same context.
Use different context to specify different behaviour of the same functionality.
Finally last step. Group shared functionality in a before block. In our example:
subject { visit articles_path }
before do
subject
end
context 'user is logged in' do
before do
login_as :user
end
it 'displays article's title' do
expect(page).to have_content(article.title)
end
it 'displays article's title' do
expect(page).to have_content(article.comment)
end
end
context 'user not logged in' do
it 'displays article's comments' do
expect(page).to have_content('Log in')
end
end
As you can see, the two contexts run the subject but only the first content logs the user in to test the article page whereas the second context don't.
I hope this was useful.
Keep testing and soon it will become a matter of habit and you will be writing tests much easily.
So I don't have a great reason for needing to know this other than curiosity - the BEST reason - but I'm not sure what's going on here.
Background:
I'm working through the RSpec book and updating the examples.
On Chapter 24 - Rails Controllers there's a test for a messages controller.
## spec/controllers/messages_controller_spec.rb ##
require 'spec_helper'
describe MessagesController do
describe "POST create" do
let(:message) { mock_model(Message).as_null_object }
before do
Message.stub(:new).and_return(message)
end
# Then a bunch of Tests...
context "when the message fails to save" do
before do
message.stub(:save).and_return(false)
post :create
end
it "assigns #message" do
assigns[:message].should eq(message)
end
it "renders the new template" do
response.should render_template("new")
end
end
end
end
This goes along with the messages controller:
## app/controllers/messages_controller.rb ##
class MessagesController < ApplicationController
def create
#message = Message.new(params[:message])
if #message.save
flash[:notice] = "The message was saved successfully"
redirect_to action: "index"
else
render "new"
end
end
end
When I run the tests:
The test passes with response.
it "renders the new template" do
response.should render_template("new")
end
The test also passes with subject.
it "renders the new template" do
subject.should render_template("new")
end
The test Also passes with page
it "renders the new template" do
page.should render_template("new")
end
The test ALSO passes with NOTHING
it "renders the new template" do
should render_template("new")
end
In case it helps anyone make heads or tails of this, the config/routes.rb just has resources :messages
Why do all those tests pass? What am I actually testing? Are 'page', 'subject', and ' ' just synonyms for response?
Does it matter as long as my tests pass?
By default, the subject would be referencing the class, which is the MessagesController.
Not defining a subject in the last test example, will implicitly set the subject to be MessagesController.
From a binding.pry, it appears that subject is an instance of the controller class:
[2] pry(#<RSpec::ExampleGroups::MyController::DescribeString::ContextString>)> subject.is_a? Class
=> false
[3] pry(#<RSpec::ExampleGroups::MyController::DescribeString::ContextString>)> subject.is_a? Users::SessionsController
=> true
I am using Ruby on Rails 3.1.0 and the rspec-rails 2gem. Since I have to test both HTML and JavaScript requests for the same controller action and since sometime those responds by rendering different view files or behaving differently, I would like to refactor some code.
Generally, in my controller file I have:
def create
...
respond_to
format.html
format.js
end
end
At this time, in order to test both JS and HTML requests\responses, in my spec file I have two different examples (one example, for each case):
context "POST create" do
let(:user) { User.new }
it "should correctly respond to a JS request" do
xhr :post, :create
...
session[:user].should be_nil
flash[:notice].should be_nil
end
it "should correctly respond to a HTML request" do
post :create
...
session[:user].should be_nil
flash[:notice].should be_nil
end
end
How could\should I refactor the above code?
You could use shared_examples_for.
context "POST create" do
let(:user) { User.new }
shared_examples_for "a succesfull request" do
it("does not set the user") { session[:user].should be_nil }
it("does not set the flash") { flash[:notice].should be_nil }
end
context "with a js request" do
before(:each) do
xhr :post, :create
end
it_should_behave_like "a succesfull request"
end
context "with a HTML request" do
before(:each) do
post :create
end
it_should_behave_like "a succesfull request"
end
end
Hope this helps.
I am trying to specify in my RSpec tests that my controller should use current_user.projects.find() instead of Project.find() I am using the Mocha mocking framework and was trying something like this:
controller.current_user.projects.expects(:find).returns(#project)
I have already mocked out controller.stubs(:current_user).returns(#profile)
This test passes with this even when I use the Project.find() implementation. How can I test that my controller is calling off of the correct object?
Edit (adding additional code):
I have Projects and Tasks, Project have many tasks. This is the show method for displaying a task in a project that is owned by current_user
Action in the controller:
def show
#project = current_user.projects.find_by_id(params[:cardset_id])
if #project.nil?
flash[:notice] = "That project doesn't exist. Try again."
redirect_to(projects_path)
else
#task = #project.tasks.find_by_id(params[:id])
end
end
This is the test that is not checking that the cardsets method was called off the current_user object.
Current Test:
context "with get to show" do
context "with valid project" do
before(:each) do
#project = Factory(:project)
#task = Factory(:task)
#profile = #project.profile
ApplicationController.stubs(:require_user).returns(true)
controller.stubs(:current_user).returns(#profile)
Project.stubs(:find_by_id).returns(#project)
#project.tasks.stubs(:find_by_id).returns(#task)
get :show, :project_id => #project.id, :id => #task.id
end
it "should assign task" do
assigns[:task].should_not be_nil
end
it "should assign project" do
assigns[:project].should_not be_nil
end
end
context "with invalid project" do
before(:each) do
Project.stubs(:find_by_id).returns(nil)
get :show, :project_id => #project.id, :id => #task.id
end
it "should set flash" do
flash[:notice].should match(/doesn't exist/i)
end
it "should redirect" do
response.should redirect_to(cardsets_url)
end
end
end
Based on the little you've told us, I think you need:
#profile.expects(:find).returns(#project)