RSpec: Expectation on model's not working while testing controller - ruby-on-rails

I'm trying to write a functional test. My test looks as following:
describe PostsController do
it "should create a Post" do
Post.should_receive(:new).once
post :create, { :post => { :caption => "ThePost", :category => "MyCategory" } }
end
end
My PostsController (a part of it actually) looks as following:
PostController < ActiveRecord::Base
def create
#post = Post.new(params[:post])
end
end
Running the test I'm always receiving a failure, which says that the Post class expected :new but never got it. Still, the actual post is created.
I'm a newbie to RSpec. Am I missing something?

EDIT - Threw away the previous rubbish
This should do what you want
require File.dirname(__FILE__) + '/../spec_helper'
describe PostsController do
it "should create a Post" do
attributes = {"Category" => "MyCategory", "caption" => "ThePost"}
Post.stub!(:new).and_return(#post = mock_model(Post, :save => false))
Post.should_receive(:new).with( attributes ).and_return #post
post :create, { :post => attributes }
end
end
This assumes you are using rspecs own mocking library and that you have the rspec_rails gem installed.

You can use the controller method of Rspec-rails to test message expectations on controllers, as described here. So one way of testing your create action is like so:
describe PostsController do
it "should create a Post" do
controller.should_receive(:create).once
post :create, { :post => { :caption => "ThePost", :category => "MyCategory" } }
end
end
EDIT (making an argument)
You might want to consider whether it's a good idea to write a test that depends on the implementation of the create action. If you're testing for anything other than the proper responsibilities of a controller, you run the risk of breaking tests when refactoring, and having to go back and rewrites tests when the implementation changes.
The job of the create action is to create something -- so test for that:
Post.count.should == 1
and then you know whether a Post was created, without depending on how it was created.
EDIT #2 (um...)
I see from your original question that you already know the Post is being created. I'd still argue that you should test for behavior, not implementation, and that checking for whether the model receives a message is not a good thing in a controller test. Maybe what you're doing is debugging, not testing?

Related

How to write Rspec for basic Show, Create, Destroy, Edit methods in rails (with or without FactoryGirl)

I have a basic user_controller.rb file like this:
class UserController < ApplicationController
def new
#user = User.new
end
def index
#user = User.all
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to action: 'index'
end
private
def user_params
params.require(:user).permit(:name, :key, :desc)
end
end
This is my (model) user.rb file:
class User < ApplicationRecord
validates :name, presence: true
validates :key, uniqueness: true, presence: true
validates :desc, presence: true
end
And created a factories.rb file (in the specs folder):
FactoryGirl.define do
factory :user do
name "TestUser"
key "TKey"
desc "TestDescription"
end
end
I tried several ways to make the specs work but I can't because of the confusing syntax.
The only one which worked was (for the 'C' in the CRUD operations, the below file is user_controller_specs.rb):
require 'rails_helper'
require 'factory_girl_rails'
RSpec.describe UserController, :type => :controller do
let(:temp) { FactoryGirl.build(:user) }
describe "POST create" do
it "should redirect back to the index page" do
post :create, :user => { :user => temp }
expect(get: user_url(subdomain: nil)).to route_to(controller: "user", action: "index")
end
end
end
I skimmed through several tutorials to find what should be the correct approach for CRUD operations but didn't got any simple to understand specs. I am trying to write these in the specs/controllers folder but these are giving errors. What should be the correct syntax to write the specs?
PS: I'am new to Ruby/Rails and trying to write test cases with Rspec and FactoryGirl. Any help is appreciated.
Edit:
Maybe I framed the question wrongly... I'm more interested in the syntax part. If I get to know an example how to write one, I'll be able to write others by changing some tiny bits of logic here and there.... Let's say I have a basic test case just to see whether updating a user details is not returning an error because of validations, how should I write it with (or without) Factory Girl gem?
It's a pretty broad question, but in any kind of test, you want test whatever use cases you have available to you. Example--are there different paths users might follow from hitting a specific controller action.
So you want your test to cover the basics. When you hit the create action, is a user actually created? If the relevant params are missing, is an error thrown? Use cases will drive your expectations.
With rspec controllers specifically, you'll use the appropriate verb and the name of the action, and pass it whatever parameters are necessary.
post :create, :user => { :user => temp }
That basically says, "do a post request to my create an action and pass it the parameters inside these curly braces."
After running that rspec gives you access to the response. You can always log the response after a controller request to help you debug the situation: p response.
You'll follow up each type of request with an expectation. The expectation should answer the question: "What did I expect hitting this action to do?" If you were, for instance, hitting the user update action and passed a param to change the user's age to 21, your expectation might be something like:
expect(user.age).to eq(21)
A great resource is the rspec documentation on relish. https://relishapp.com/rspec
"How to" do a broad general thing is a tough question to answer like this. My advice would be to try to actually test one, log the failure case, and post those logs in a new question and people on SO can help you work through testing a particular action you're struggling with.

Testing POST #create in RSpec with nested attributes - can't get params hash correct

Have been spending some months trying to grok RSpec/TDD. Running into some challenges testing a controller with a nested attribute. I'm a bit fuzzy about how to properly set up and pass the parameters to the controller. I can build the controller so that it actually works as expected in the browser - I just can't seem to reverse engineer a test to confirm that.
Would appreciate any recommendations to a) fix the test below, and b) advise any better ways to do it (e.g. with mocks, stubs, etc).
Here's the basic model structure:
class School < ActiveRecord::Base
has_many :scholarships
end
class Scholarship < ActiveRecord::Base
belongs_to :school
end
I've configured the routes.rb as you'd expect, with:
resources :schools do
resources :scholarships, only: [:new, :create, :destroy]
end
In the controller, #new and #create are pretty standard for a Rails app:
class ScholarshipsController < ApplicationController
def new
#school = School.find(params[:school_id])
#scholarship = #school.scholarships.build
end
def create
#school = School.find(params[:school_id])
#scholarship = #school.scholarships.create(scholarship_params)
if #scholarship.save
flash[:success] = 'Scholarship created!'
redirect_to #school
else
render 'new'
end
end
private
def scholarship_params
params.require(:scholarship).permit(:name, ** Lots of params omitted for brevity **,
:notes, school: [:id])
end
end
The spec is where I can't seem to figure things out. For spec/controllers/scholarships_controller_spec.rb:
require 'rails_helper'
describe ScholarshipsController, type: :controller do
describe 'POST #create' do
context 'with valid attributes' do
before :each do
#school = create(:school)
#scholarship = #school.scholarships.create(FactoryGirl.build(:scholarship).attributes)
end
it 'receives :save' do
post :create, { scholarship: #scholarship.attributes, school: #school.id }
expect(#scholarship).to receive(:save)
end
end
end
end
When I run that test, I get the following error:
Failures:
1) ScholarshipsController POST #create with valid attributes receives :save
Failure/Error: post :create, scholarship: { attributes: #scholarship.attributes, school: #school.id } #school_id: #school.id, scholarship: #scholarship
ActionController::UrlGenerationError:
No route matches {:action=>"create", :controller=>"scholarships",
:scholarship=>{"id"=>"1", "name"=>"Dynamic Metrics Director Scholarship", *** Lots of parameters omitted for brevity ***
, "school_id"=>"2"}, :school=>"2"}
The parameters look correct to me. there's a set of attributes for the scholarship, and for the school. But the routing isn't working. I've tried a dozen different ways to try and get this to work. Heartened that I'm apparently passing a (more or less correct) parameters hash, but can't figure out quite where I'm going wrong.
****** EDIT ******
Updated in response to an answer posted below.
Changed the syntax of the spec as suggested by Srdjan:
it 'receives :save' do
post :create, "schools/#{#school.id}/scholarships", { scholarship: #scholarship.attributes, school_id: #school.id }
expect(#scholarship).to receive(:save)
end
This changes the error message. I assume that indicates that the parameters are being passed correctly, since it's no longer throwing an error related to routes/params..? Error message is:
1) ScholarshipsController POST #create with valid attributes receives :save
Failure/Error: expect(#scholarship).to receive(:save)
(#<Scholarship:0x007fe293b02598>).save(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Just for good measure, here are the relevant routes, which I hadn't posted previously:
school_scholarships POST /schools/:school_id/scholarships(.:format) scholarships#create
new_school_scholarship GET /schools/:school_id/scholarships/new(.:format) scholarships#new
school_scholarship DELETE /schools/:school_id/scholarships/:id(.:format) scholarships#destroy
In your test, you're POST-ing to the wrong route. As setup in routes.rb, scholarship resources do not exist out of the context of a school resource.
In order to fix this, you have to answer a question: "Does it make sense for a user to access a scholarship record without having to specify a school?"
If the answer is yes, you can either copy the scholarships route and paste them outside of the schools resource block. This way, you can have access to scholarships without having to specify a school, but also with specifying a school.
If the answer to the question is no, then you need to fix your test as such:
it 'receives :save' do
post :create, "schools/#{#school.id}/scholarhips", { scholarship: #scholarship.attributes, school_id: #school.id }
expect(#scholarship).to receive(:save)
end

Using Shoulda redirect_to to test a controller's create action

I'm using RSpec + Shoulda to test my RESTful controller in Rails 3. I'm having trouble figuring out how to test the create action's redirect. The standard RESTful controller should redirect to the show action for the new post. For example, if I have a ProjectsController for a Project model, then upon successful create, that action should:
redirect_to project_url(#project)
Shoulda provides a handy redirects_to macro for handling this. Here is what I have tried:
describe ProjectsController, '#create' do
context "Anonymous user" do
before :each do
#attrs = Factory.attributes_for(:project_with_image)
post :create, :project => #attrs
end
it { should assign_to(:project) }
it { should respond_with(:redirect) }
it { should redirect_to(#project) }
end
end
(Yes, I'm using FactoryGirl, but since I'm only using it for attributes in this case, it shouldn't matter. I think.)
How do I specify the last test there? It should redirect_to(...) what? I've tried #project, project_url(#project).. But I can't figure it out.
Looking at the Shoulda matcher code, I noticed that the redirect_to matcher can accept a block. But I'm not sure how to access the newly created #project object in that block...
Any thoughts?
Haven't tried it, but the problem probably is, that #project is not available in your spec. How about it {should redirect_to(Project.last) } or it {should redirect_to(assigns(:project)) }?

RSpec mocking a nested model in Rails - ActionController problem

I am having a problem in RSpec when my mock object is asked for a URL by the ActionController. The URL is a Mock one and not a correct resource URL.
I am running RSpec 1.3.0 and Rails 2.3.5
Basically I have two models. Where a subject has many notes.
class Subject < ActiveRecord::Base
validates_presence_of :title
has_many :notes
end
class Note < ActiveRecord::Base
validates_presence_of :title
belongs_to :subject
end
My routes.rb file nests these two resources as such:
ActionController::Routing::Routes.draw do |map|
map.resources :subjects, :has_many => :notes
end
The NotesController.rb file looks like this:
class NotesController < ApplicationController
# POST /notes
# POST /notes.xml
def create
#subject = Subject.find(params[:subject_id])
#note = #subject.notes.create!(params[:note])
respond_to do |format|
format.html { redirect_to(#subject) }
end
end
end
Finally this is my RSpec spec which should simply post my mocked objects to the NotesController and be executed... which it does:
it "should create note and redirect to subject without javascript" do
# usual rails controller test setup here
subject = mock(Subject)
Subject.stub(:find).and_return(subject)
notes_proxy = mock('association proxy', { "create!" => Note.new })
subject.stub(:notes).and_return(notes_proxy)
post :create, :subject_id => subject, :note => { :title => 'note title', :body => 'note body' }
end
The problem is that when the RSpec post method is called.
The NotesController correctly handles the Mock Subject object, and create! the new Note object. However when the NoteController#Create method tries to redirect_to I get the following error:
NoMethodError in 'NotesController should create note and redirect to subject without javascript'
undefined method `spec_mocks_mock_url' for #<NotesController:0x1034495b8>
Now this is caused by a bit of Rails trickery that passes an ActiveRecord object (#subject, in our case, which isn't ActiveRecord but a Mock object), eventually to url_for who passes all the options to the Rails' Routing, which then determines the URL.
My question is how can I mock Subject so that the correct options are passed so that I my test passes.
I've tried passing in :controller => 'subjects' options but no joy.
Is there some other way of doing this?
Thanks...
Have a look at mock_model, which is added by rspec-rails to make it easier to mock ActiveRecord objects. According to the api docs:
mock_model: Creates a mock object instance for a model_class with common methods stubbed out.
I'm not sure if it takes care of url_for, but it's worth a try.
Update, 2018-06-05:
As of rspec 3:
mock_model and stub_model have been extracted into the rspec-activemodel-mocks gem.
In case zetetic's idea doesn't work out, you can always say Subject.new and then stub out to_param and whatever else you might need faked for your example.

How to make fixtures updateable in Rails' tests?

Below I listed some code from simple Rails application. The test listed below fails in last line, because the updated_at field of the post is not changed within the update action of PostController in this test. Why?
This behaviour seems to me a little strange, because standard timestamps are included in Post model, live testing on local server shows that this field is actually updated after returning from update action and first assertion is fulfilled thus it shows the update action went ok.
How can I make fixtures updateable in above meaning?
# app/controllers/post_controller.rb
def update
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post # Update went ok!
else
render :action => "edit"
end
end
# test/functional/post_controller_test.rb
test "should update post" do
before = Time.now
put :update, :id => posts(:one).id, :post => { :content => "anothercontent" }
after = Time.now
assert_redirected_to post_path(posts(:one).id) # ok
assert posts(:one).updated_at.between?(before, after), "Not updated!?" # failed
end
# test/fixtures/posts.yml
one:
content: First post
posts(:one)
That means "fetch the fixture named ":one" in posts.yml. That's never going to change during a test, barring some extremely weird and destructive code that has no place in sane tests.
What you want to do is check the object that the controller is assigning.
post = assigns(:post)
assert post.updated_at.between?(before, after)
On a side note if you were using shoulda (http://www.thoughtbot.com/projects/shoulda/) it would look like this:
context "on PUT to :update" do
setup do
#start_time = Time.now
#post = posts(:one)
put :update, :id => #post.id, :post => { :content => "anothercontent" }
end
should_assign_to :post
should "update the time" do
#post.updated_at.between?(#start_time, Time.now)
end
end
Shoulda is awesome.

Resources