Rails mock_model returning TrueClass? - ruby-on-rails

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

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

Rails3 fails validation and loses child fields

I have a form that prompts for a job and job_files to attach. When the job save fails due to validation, the selected job_files disappear when the form redisplays. How do I retain the child fields? All parent fields are retained in the form but the child fields are gone.
job model:
class Job < ActiveRecord::Base
has_many :job_files, :dependent => :destroy
accepts_nested_attributes_for :job_files, :allow_destroy => true
validates_acceptance_of :disclaimer, :allow_nil => false, :accept => true, :on => :create, :message=>'Must accept Terms of Service'
end
job_files model:
class JobFile < ActiveRecord::Base
belongs_to :job
has_attached_file :file
end
jobs_controller:
def new
#upload_type = UploadType.find_by_id(params[:upload_job_id])
#job = Job.new
#job.startdate = Time.now
10.times {#job.job_files.build}
#global_settings = GlobalSettings.all
end
def create
#global_settings = GlobalSettings.all
if params[:cancel_button]
redirect_to root_path
else
#job = Job.new(params[:job])
#job.user_id = current_user.id
#upload_type = UploadType.find_by_id(#job.upload_type_id)
if #job.save
JobMailer.job_uploaded_notification(#upload_type,#job).deliver
flash[:notice] = "Job Created"
redirect_to root_path
else
# retain selected files if save fails
(10 - #job.job_files.size).times { #job.job_files.build }
flash[:error] = "Job Creation Failed"
render :action => :new
end
end
end
job_files partial within job form:
<%= form.fields_for :job_files, :html=>{ :multipart => true } do |f| %>
<li class="files_to_upload">
<%= f.file_field :file %>
</li>
<% end %>
Any help would be appreciated since I can not find any solution online.
That's standard-issue behavior. Really, the only way to workaround it would be to persist the files first, regardless of whether validation passes. Then, run your validations. But that's definitely a workaround.
You could also do client-side validations before the server-side ones; that way, you'd have better assurances that the user's data is valid BEFORE they submit to the server, such that they won't encounter any server-side validation failures, and the files will always make it. I'd probably go that route if I were in your shoes. Note that client-side validation is NOT a replacement for server-side validation, just will help your users in this case.
Hope that helps!

Rails redirect_to REST

I'm in the process of learning RoR, and I'm liking everything that I'm discovering so far. I'm switching over from the PHP-based CodeIgniter framework, and I have a problem in using the redirect_to method.
I've defined a basic Users model which I use to handle registration - data gets stored fine in the DB, but the problem is when redirecting after signing up a user to the system.
Basically, the profile page is of the following format: /users/:name/:id
I have a routes file defined as such:
resources :users
match '/users/:name/:id', :to => 'users#show'
And here is my create method
def create
#title = "User creation"
#user = User.new(params[:user])
if #user.save
info = { :name => #user.name, :id => #user.id }
redirect_to info.merge(:action => "show")
else
#title = 'Sign Up'
render 'new'
end
end
However this will generate an url of the following format:
http://localhost:3000/users/27?name=Testing
When I'm actually looking for something like this:
http://localhost:3000/users/Testing/27
It just makes sense for me from a SEO point of view that a profile page URL look like that. I've been searching the inter-webs, but I only find solutions to different problems. I hope someone can help.
SOLVED
Both versions that were suggested by Ryan worked fine, and I decided to stick to the second one, since it feels more RESTful. I'll just share the config I have right now - mind you, the User model may not be all that correct, but it's the to_param function that's important. Also, I've noticed that it doesn't work if I make the function private - that makes sense, but I just thought that I'd share that for someone that may be running into that sort of problem.
Here's my routes file:
resources :users
And here is my Users model:
class User < ActiveRecord::Base
attr_accessible :name, :email
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name,
:presence => true,
:length => { :within => 5..50 }
validates :email,
:presence => true,
:format => { :with => email_regex},
:uniqueness => { :case_sensitive => false }
def to_param
"#{id}-#{name.parameterize}"
end
end
And here's my controller create function:
def create
#title = "User creation"
#user = User.new(params[:user])
if #user.save
redirect_to #user
else
#title = 'Sign Up'
render 'new'
end
end
Define your route like this:
get '/users/:name/:id', :to => 'users#show', :as => "user"
Then redirect to it using this helper:
redirect_to(user_path(#user.name, #user.id))
Alternatively, you could just stick with resources :users and not have to define your own route. The difference here is that your route would be something like /users/1-testing rather than users/1/testing, but the advantage is that you would be more Rails standard.
To do this, define a to_param method in your model, like this:
def to_param
"#{id}-#{name.parameterize}
end
Then Rails will use the output of the to_param method in your routes.

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

How do you specify POST params in a Rails test?

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'

Resources