How do you specify POST params in a Rails test? - ruby-on-rails

Working with Test::Unit and Shoulda. Trying to test Users.create. My understanding is that Rails forms send params for an object like this:
user[email]
Which turns into hash in your action, right?
params[:user][:email]
OK, so in my test I've tried...
setup { post :create, :post => { 'user[email]' => 'invalid#abc' } }
and
setup { post :create, :post => { :user => { :email => 'abc#abcd' } } }
In both cases, over in my action, params[:user] is nil.

post :create, :user => { :email => 'foo#bar.com' }
The general form for all the test methods of get, post, put, delete are as follows:
def post(action_name, params_hash = {}, session_hash = {})
And in tests, the params hash gets directly sent into params of your controller action with no translation of any sort. Even doing integration testing you really shouldnt need to test this string to params translation as its covered very well by the rails framework tests. Plus all testing methods that need params accept a hash in this manner without complaint making things easy for you.

post :create, {:post => {}, :user => {:email => 'abc#abcd'} }
In this case params[:post] is {},
params[:user] is {:email => 'abc#abcd'},
params[:user][:email] is 'abc#abcd'.
post :create, {:post => {:user => {:email => 'abc#abcd'} } }
In this case params[:post][:user][:email] is 'abc#abcd'

Related

Rails 4 strong parameters with custom nested attributes name

I want to change the name of the attribute in strong parameter so it does not have "_attributes" in the end.
I have:
params.require(:setting).permit(:recording,
:special_settings_attributes => [:orientation])
I am testing it with :
describe "Settings Creation" do
context 'new setting success' do
before do
a = post :create, format: :json, :setting => {
:recording => "recorded",
:special_settings_attributes => [:orientation => "left"]
}
end
it 'creates a new setting' do
expect(Setting.last.special_settings.last.orientation).to eq("left")
end
end
end
end
I want
params.require(:setting).permit(:recording,
:special_settings => [:orientation])
I tried renaming of course, but then the SpecialSetting model is no created..
Just alter your params before it's called/used by any of your actions:
before_action do
params[:special_settings_attributes] ||= params.delete :special_settings
end

Rails Rspec/FactoryGirl not passing correct params to controller

I am trying to write an Rspec test to test one of my controllers in Rails but I am having a problem getting the correct params hash.
My create method in my Activities Controller looks like this(The update method is identical). As you can see the params hash it receives has an :activity, :start_time, and :end_time hash.
def create
#activity = Activity.new(params[:activity])
#activity.start_time = to_interval(params[:start_time])
#activity.end_time = to_interval(params[:end_time])
respond_to do |format|
....
The rspec activities controller spec looks like
it "redirects to the activity" do
activity = Activity.create! valid_attributes
put :update, :id => activity.id, :activity => valid_attributes
response.should redirect_to(activity)
end
The second line calls valid attributes
def valid_attributes
FactoryGirl.attributes_for(:activity)
end
which returns a hash
{"id"=>"1", "name"=>"Some Activity1"}
and when the controller receives it the hash looks like
{"activity"=>{"id"=>"1", "name"=>"Some Activity1"}, "controller"=>"activities", "action"=>"create"}
As you can see the :start_time and :end_time hashes are missing. In the end I need a hash which looks like
{"activity"=>{"id"=>"1", "name"=>"Some Activity1"}, "start_time"=>{"hours"=>"1", "minutes"=>"0"}, "end_time"=>{"hours"=>"1", "minutes"=>"0"}, "controller"=>"activities", "action"=>"create"}
I cannot figure out the correct syntax for adding the :start_time and :end_time hashes to the params hash. I cannot combine it with the hash returned from FactoryGirl because the create method combines the three hashes into the form below which is incorrect because now :start_time and :end_time are embedded in the :activities hash.
{"activity"=>{"id"=>"1", "name"=>"Some Activity1", "start_time"=>{"hours"=>"1", "minutes"=>"0"}, "end_time"=>{"hours"=>"1", "minutes"=>"0"}}, "controller"=>"activities", "action"=>"create"}
Any help or hints are appreciated and I thank you for you time and help!!
I find something weird in your code. You are trying to put :update, but you're looking at the create action (which may also be somethings that contributes to the issue, your test for the create action could already have the params.)
For your question, you should be able to do this to pass the start_time and end_time parameters
put :update, :id => activity.id, :activity => valid_attributes, :start_time => { :hour => 1, :minute => 2 }, :end_time => { :hour => 1, :minute => 2 }

Simple rspec question

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
...

Rails mock_model returning TrueClass?

Trying to test a controller in Rspec. (Rails 2.3.8, Ruby 1.8.7, Rspec 1.3.1, Rspec-Rails 1.3.3)
I'm trying to post a create but I get this error message:
ActiveRecord::AssociationTypeMismatch in 'ProjectsController with appropriate parameters while logged in: should create project'
User(#2171994580) expected, got TrueClass(#2148251900)
My test code is as follows:
def mock_user(stubs = {})
#user = mock_model(User, stubs)
end
def mock_project(stubs = {})
#project = mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
#lifecycletype = mock_model(Lifecycletype, stubs)
end
it "should create project" do
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" }) }
assigns[:project].should == mock_project({ :name => "Mock Project",
:description => "Mock Description",
:owner => mock_user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" })})
flash[:notice].should == "Project was successfully created."
end
The trouble comes when I try to do :owner => #user in the code above. For some reason, it thinks that my #user is TrueClass instead of a User class object. Funny thing is, if I comment out the post :create code, and I do a simple #user.class.should == User, it works, meaning that #user is indeed a User class object.
I've also tried
:owner => mock_user
:owner => mock_user({ :name => "User",
:email => "user#email.com",
:password => "password",
:password_confirmation => "password })
:owner => #current_user
Note #current_user is also mocked out as a user, which I tested (the same way, #current_user.class.should == User) and also returns a TrueClass when I try to set :owner.
Anybody have any clue why this is happening?
Thank you!
From what I can see, you are not creating your instance variable, #user before referencing it in the post statement. You would do well to create the instance variables prior to the post so the preconditions are immediately obvious. That way you could know whether #user had been set.
I know some people prefer the one-line-of-code-is-better-because-i'm-smart method of writing stuff like this, but I've found being explicit and even repetitive is a really good idea, particularly in tests.
I'm adding the following code that I believe may express your intent better that what you have. In my code, I use mock expectations to "expect" a Project is created with a particular set of parameters. I believe your code assumes that you can do an equality comparison between a newly-created mock Project and a different one created during execution of your controller. That may not be true because they are distinctly different objects.
In my code, if you have a problem with something evaluating to TrueClass or the like, you can use a line of code like user.should be_a(User) to the example to make sure stuff is wired up correctly.
def mock_user(stubs = {})
mock_model(User, stubs)
end
def mock_project(stubs = {})
mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
mock_model(Lifecycletype, stubs)
end
it "should create project" do
user = mock_user
owner = user
lifecycletype = mock_lifecycletype({ :name => "Mock Lifecycle" })
# Not certain what your params to create are, but the argument to with
# is what the params are expected to be
Project.should_receive(:create).once.with({:user => user, :owner => owner, :lifecycletype => lifecycletype})
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => lifecycletype }
flash[:notice].should == "Project was successfully created."
end

Pretty (dated) RESTful URLs in Rails

I'd like my website to have URLs looking like this:
example.com/2010/02/my-first-post
I have my Post model with slug field ('my-first-post') and published_on field (from which we will deduct the year and month parts in the url).
I want my Post model to be RESTful, so things like url_for(#post) work like they should, ie: it should generate the aforementioned url.
Is there a way to do this? I know you need to override to_param and have map.resources :posts with :requirements option set, but I cannot get it all to work.
I have it almost done, I'm 90% there. Using resource_hacks plugin I can achieve this:
map.resources :posts, :member_path => '/:year/:month/:slug',
:member_path_requirements => {:year => /[\d]{4}/, :month => /[\d]{2}/, :slug => /[a-z0-9\-]+/}
rake routes
(...)
post GET /:year/:month/:slug(.:format) {:controller=>"posts", :action=>"show"}
and in the view:
<%= link_to 'post', post_path(:slug => #post.slug, :year => '2010', :month => '02') %>
generates proper example.com/2010/02/my-first-post link.
I would like this to work too:
<%= link_to 'post', post_path(#post) %>
But it needs overriding the to_param method in the model. Should be fairly easy, except for the fact, that to_param must return String, not Hash as I'd like it.
class Post < ActiveRecord::Base
def to_param
{:slug => 'my-first-post', :year => '2010', :month => '02'}
end
end
Results in can't convert Hash into String error.
This seems to be ignored:
def to_param
'2010/02/my-first-post'
end
as it results in error: post_url failed to generate from {:action=>"show", :year=>#<Post id: 1, title: (...) (it wrongly assigns #post object to the :year key). I'm kind of clueless at how to hack it.
Pretty URLs for Rails 3.x and Rails 2.x without the need for any external plugin, but with a little hack, unfortunately.
routes.rb
map.resources :posts, :except => [:show]
map.post '/:year/:month/:slug', :controller => :posts, :action => :show, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
application_controller.rb
def default_url_options(options = {})
# resource hack so that url_for(#post) works like it should
if options[:controller] == 'posts' && options[:action] == 'show'
options[:year] = #post.year
options[:month] = #post.month
end
options
end
post.rb
def to_param # optional
slug
end
def year
published_on.year
end
def month
published_on.strftime('%m')
end
view
<%= link_to 'post', #post %>
Note, for Rails 3.x you might want to use this route definition:
resources :posts
match '/:year/:month/:slug', :to => "posts#show", :as => :post, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
Is there any badge for answering your own question? ;)
Btw: the routing_test file is a good place to see what you can do with Rails routing.
Update: Using default_url_options is a dead end. The posted solution works only when there is #post variable defined in the controller. If there is, for example, #posts variable with Array of posts, we are out of luck (becase default_url_options doesn't have access to view variables, like p in #posts.each do |p|.
So this is still an open problem. Somebody help?
It's still a hack, but the following works:
In application_controller.rb:
def url_for(options = {})
if options[:year].class.to_s == 'Post'
post = options[:year]
options[:year] = post.year
options[:month] = post.month
options[:slug] = post.slug
end
super(options)
end
And the following will work (both in Rails 2.3.x and 3.0.0):
url_for(#post)
post_path(#post)
link_to #post.title, #post
etc.
This is the answer from some nice soul for a similar question of mine, url_for of a custom RESTful resource (composite key; not just id).
Ryan Bates talked about it in his screen cast "how to add custom routes, make some parameters optional, and add requirements for other parameters."
http://railscasts.com/episodes/70-custom-routes
This might be helpful. You can define a default_url_options method in your ApplicationController that receives a Hash of options that were passed to the url helper and returns a Hash of additional options that you want to use for those urls.
If a post is given as a parameter to post_path, it will be assigned to the first (unnassigned) parameter of the route. Haven't tested it, but it might work:
def default_url_options(options = {})
if options[:controller] == "posts" && options[:year].is_a?Post
post = options[:year]
{
:year => post.created_at.year,
:month => post.created_at.month,
:slug => post.slug
}
else
{}
end
end
I'm in the similar situation, where a post has a language parameter and slug parameter. Writing post_path(#post) sends this hash to the default_url_options method:
{:language=>#<Post id: 1, ...>, :controller=>"posts", :action=>"show"}
UPDATE: There's a problem that you can't override url parameters from that method. The parameters passed to the url helper take precedence. So you could do something like:
post_path(:slug => #post)
and:
def default_url_options(options = {})
if options[:controller] == "posts" && options[:slug].is_a?Post
{
:year => options[:slug].created_at.year,
:month => options[:slug].created_at.month
}
else
{}
end
end
This would work if Post.to_param returned the slug. You would only need to add the year and month to the hash.
You could just save yourself the stress and use friendly_id. Its awesome, does the job and you could look at a screencast by Ryan Bates to get started.

Resources