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.
Related
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.
I'm really struggling trying to learn rspec :( So I hope you can give me a little bit of help with a really simple create-action in the controller. I would like to use Rspec::mocks for this, as I think that is the way to do it? Instead of having to hit the database when testing.
I'm having a before_filter:
def find_project
#project= Project.find_by_id(params[:project_id])
end
The create action looks like this:
def create
#batch = Batch.new(params[:batch])
#batch.project = #project
if params[:tasks]
params[:tasks][:task_ids].each do |task_id|
#batch.tasks << Task.find(task_id)
end
end
if #batch.save
flash[:notice] = "Batch created successfully"
redirect_to project_batch_url(#project, #batch)
else
render :new
end
end
I'm really in doubt when it comes to #batch.project = #project how do I define #project? And also the whole params[:tasks][:task_ids].each part.. Ya.. pretty much the whole thing :(
Sorry for this newbie question - Hope you guys can help or atleast point me in the right direction :)
Thanks
The idea of a controller spec is to check whether the actions are setting instance variables, and redirecting/rendering as needed. To set up the spec, you would normally create an object or a mock, set attributes/stubs, and then call the action, passing a params hash if necessary.
So for example (air code):
describe MyController do
before(:each) do
#project = mock_model(Project)
Project.stub(:find_by_id) {#project}
#batch = mock_model(Batch)
Batch.stub(:new) {#batch}
end
it "should redirect to project_batch_url on success" do
#batch.stub(:save) {true)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should redirect_to(project_batch_url(#project,#batch))
end
it "should render :new on failure" do
#batch.stub(:save) {false)
post :create, :batch => { :some_key => :some_value }, :tasks => { :task_ids => [1,2,3] }
response.should render_template("new")
end
end
You can find lots more information about this in the RSpec Rails docs.
Using BDD helps you define your interfaces. So if your controller wants the project to create a batch and add some task id's, then "write the code you wish you had." In practice for controllers, this means trying to push logic out of the controller and into your models. Testing models tends to be more intuitive and are definitely faster than testing controllers.
Here are some possible specs (untested) from the "mockist" point of view:
# controller spec
describe BatchesController do
def mock_project(stubs={})
#mock_project ||= mock_model(Project, stubs)
end
def mock_batch(stubs={})
#mock_batch ||= mock_model(Batch, stubs)
end
context "POST create"
it "calls #create_batch_and_add_tasks on the project"
mock_project.should_receive(:create_batch_and_add_tasks).with(
:batch => { :name => 'FooBatch' },
:task_ids => [1,2,3,4]
)
Project.stub(:find).and_return(mock_project)
post :create, :batch => { :name => 'FooBatch' }, :tasks => { :task_ids => [1,2,3,4] }
# consider changing your params to :batch => { :name => 'FooBatch', :task_ids => [1,2,3,4] }
end
it "redirects to the project_batch_url on success" do
mock_project(:create_batch_and_add_tasks => mock_batch(:save => true))
Project.stub(:find) { mock_project }
post :create, :these_params => "don't matter because you've stubbed out the methods"
end
# controller
def create
#batch = #project.create_batch_and_add_tasks(
:batch => params[:batch],
:task_ids => params[:tasks].try([:tasks_ids])
)
if #batch.save
...
I have a polymorphic model called Address, I am trying to currently write some basic function tests for this model and controller. For the controller I am at a loss on how to go about this. For example I have another model called Patient, each Patient will have an address, so i have started writing the following function test, but i have no idea how to use "get" with a nested polymorphic resource. Now I was able to find some polymorphic test information on Fixtures here: http://api.rubyonrails.org/classes/Fixtures.html
but this will not help me test against the index. Any help is much appreciated im at a total and complete loss here.
FILE: test/functional/addresses_controller_test.rb
require 'test_helper'
class AddressesControllerTest < ActionController::TestCase
setup do
#address = addresses(:of_patient)
#patient = patients(:one)
activate_authlogic
end
test "patient addresses index without user" do
get :index <<<<<<<<<<<< what goes here????
assert_redirected_to :login
end
end
Assuming your controller is setup the way I think it might be:
def index
if #current_user
#addresses = #current_user.addresses.all
else
redirect_to login_path
end
end
Then the test will probably look like this:
test "patient addresses index without user" do
get :index, :patient_id => #patient.id
assert_redirected_to :login
end
test "patient addresses with user" do
#current_user = #patient
get :index, :patient_id => #patient.id
assert_response :success
end
The thing to keep in mind is that the index method needs the patient_id to process.
I have a fairly typical require_no_user as a before_filter in one of my controllers. I need to test that a logged in user is redirected by this filter if they try to access any of the controller's actions.
Is there a sensible way to do this without enumerating all of the controller's actions in my test case?
I'm trying to avoid:
context 'An authenticated user' do
setup do
activate_authlogic
#user = Factory(:user)
UserSession.create(#user)
do
should 'not be allowed to GET :new' do
get :new
assert_redirected_to(root_path)
end
should 'not be allowed to POST :create' do
# same as above
end
# Repeat for every controller action
end
Not that I'm aware of... though you could make it a bit shorter by packing all the methods and actions into a hash:
should "be redirected" do
{
:get => :new,
:post => :create,
}.each do |method, action|
send(method, action)
assert_redirected_to(root_path)
end
end
Edit: so yeah, this is probably overkill, but here's another way:
should "be redirected" do
ActionController::Routing::Routes.named_routes.routes.each do |name, route|
if route.requirements[:controller] == #controller.controller_name
send(route.conditions[:method], route.requirements[:action])
assert_redirected_to(root_path)
end
end
end
Seems though that if you define multiple :methods in custom routes that it still only "finds" the first, e.g.
map.resources :foo, :collection => {
:bar => [:get, :post]
}
The above route will only be attempted with the GET verb.
Also if there are other requirements in the URL, such as presence of a record ID, my naive example ignores that requirement. I leave that up to you to hack out :)
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?