I am writing my first controller tests for Rails in Rspec. In testing the create action, I want to write a test that verifies that the 'new' template is rendered when the form is submitted with invalid attributes. In this case, that means a field is left blank.
I have found several examples that I have tried to follow verbatim. However, when using Rspec to trigger the create action, it seems like ActiveRecord actually tries to create that record which, due to validations, fails. Thus, my test fails before I even get to test the redirect function. This does not seem to be an issue for the people writing the tutorials or StackOverflow answers.
What is the best way to test the create action in a controller, given invalid attributes?
Rails 4.0.0, Ruby 2.0.0, Rspec 3.0.0.beta2
order_items_controller_spec
describe "POST create" do
context "with invalid attributes" do
it "re-renders the new method" do
post :create, order_item: FactoryGirl.attributes_for(:order_item, :buyer_id => nil)
expect( response ).to render_template :new
end
end
end
order_items_controller
def create
#order_item = OrderItem.new(order_item_params)
respond_to do |format|
if #order_item.save!
format.html { redirect_to cart_path(#order_item), notice: 'Your trip has been added to your cart.' }
format.json { render action: 'show', status: :created, location: #order_item }
else
format.html { redirect_to new_order_item_path, notice: 'We were unable to customize your trip.' }
format.json { render json: #order_item.errors, status: :unprocessable_entity }
end
end
end
rspec error message:
OrderItemsController POST create with invalid attributes re-renders the new method
Failure/Error: post :create, order_item: FactoryGirl.attributes_for(:order_item, :buyer_id => nil)
ActiveRecord::RecordInvalid:
Validation failed: Buyer can't be blank
# ./app/controllers/order_items_controller.rb:30:in `block in create'
# ./app/controllers/order_items_controller.rb:29:in `create'
# ./spec/controllers/order_items_controller_spec.rb:43:in `block (4 levels) in <top (required)>'
Thanks in advance.
Your test looks good; it's actually your code which is failing to work as you expect when the save fails. When you say "ActiveRecord actually tries to create that record which, due to validations, fails", that's exactly what you want your test to be doing, because you are trying to test what happens if the validation fails.
In your controller you are saving the record with save!. This will cause an error be raised if the save fails, which is why you immediately see a failure in rspec rather than it going on to the new view.
Instead you want to use save (without the bang). This will return true/false depending on the save success so you can use it in a condition.
Your controller code should be:
def create
#order_item = OrderItem.new(order_item_params)
respond_to do |format|
if #order_item.save ### ! removed
...
Don't use 'save!' ... you should be using 'save' which will return false if not valid.
Related
#Destroy Action method
def destroy
Key.destroy(params[:id])
end
-------------------------------
#testcase for this
expect(delete :destroy ,{"id"=>"3"}).to change(Key, :count).by(-1)
It is showing Action view::template missing error can't I write test case for void action(returns nothing/render nothing) methods in rails.
Now i changed my method to render the json value.
def destroy
Key.destroy(params[:id])
respond_to do |format|
format.html {}
format.json { head :no_content }
end
end
Now my assertion for to run test case is:
it "should destroy" do
response=delete :destroy ,{"id"=>"3"}
expect(response).to have_http_status(204)
end
You can create a sample item with a Factory (for example Factory Girl) and then check if it exists or not. This is a sample destroy test (the query is for a Mongoid db instead, but you can get it.
In my case, my destroy method has a json response which can be checked as well (but I don't do it here).
let(:key_to_delete) {FactoryGirl.create(:key)}
it 'should destroy' do
delete :destroy, id: key_to_delete.id
expect(Key.where(id: key_to_delete.id).count).to eq(0)
end
I'm targetting 100% coverage on a fresh new rails application. I only have one resource, auto-generated.
For creation and update operations, the generated source code contains a "else" case, triggered on database failure (save has failed).
# POST /projects
# POST /projects.json
def create
#project = Project.new(project_params)
#project.user = current_user
respond_to do |format|
if #project.save
format.html { redirect_to projects_url, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
As It's part of my controller (and may include dedicated behavior like redirection or error message), I want to test it in unit test, under MiniTest.
I have a beginning of solution with creating a test containing ActiveRecord::Base.remove_connection. I also create a teardown function containing the opposite : ActiveRecord::Base.estabish_connection
test "should not create project" do
assert_equal('Project.count') do
ActiveRecord::Base.remove_connection
post :create, project: { name: #project.name, user_id: #project.user_id }
end
assert_redirected_to projects_path
end
And to restore connection for other tests :
teardown do
ActiveRecord::Base.establish_connection
end
It's not acceptable for (at least) two reasons :
the test finishes in a exception ConnectionNotEstablished which is not the goal of the test, and after that I can't verify the behavior of the controler itself (redirection)
establish_connection is performed on every test (useless 90% of time)
Thanks for your suggestions !
EDIT : added code source
As I mentioned in my comment, I think you want to use a mock instead of disconnecting your database. Since you are using #project.save to check if the save worked, we want it to return false instead of raising a ConnectionNotEstablished error.
We can try to use MiniTest::Mock to accomplish this, but other people might use an external mocking library like Mocha.
Note: I don't use minitest, so this is my best guess
test "should not create project" do
Project.expect(:find, #project) do
#project.expect(:save, false) do
assert_equal('Project.count') do
post :create, project: { name: #project.name, user_id: #project.user_id }
end
end
end
assert_redirected_to projects_path
end
If you use mocha, it might look something like this:
test "should not create project" do
Project.any_instance.stubs(:save).returns(false)
assert_equal('Project.count') do
post :create, project: { name: #project.name, user_id: #project.user_id }
end
assert_redirected_to projects_path
end
I have an Admin model which can manage Organizations.
I have an AdminController with a simple index action and a child Admin::OrganizationsController controller which I'm trying to test.
The test for the canonical show action on this child controller passes without errors:
describe "GET show" do
it "assigns the requested organization as #organization" do
org = FactoryGirl.create(:organization)
get :show, id: org.id # <---- this works
expect(assigns(:organization)).to eq(org)
end
end
but when I try to test the destroy action, I get an error I'm not able to understand (hence resolve):
describe "DELETE destroy" do
it "destroys the requested organization" do
org = FactoryGirl.create(:organization)
delete :destroy, id: org.id # <---- (I tried to use org.id.to_param, unsuccessfully)
# ...rest of the test
end
end
with error:
Failure/Error: expect { delete :destroy, id: org.id }.to change(Organization, :count).by(-1)
NameError:
undefined local variable or method `organizations_url' for #<Admin::OrganizationsController:0x007fefe1622248>
I suspect this has to do with my controller being "nested" (it needs something like admin_organizations_url I guess).
Any help?
(additional side infos: Rails 4.0.1, rspec 3.0.0.beta1)
"Inspired" by CDub's comment, I took a look at the destroy action in Admin::OrganizationController, which looked like this:
def destroy
#organization.destroy
respond_to do |format|
format.html { redirect_to organizations_url } # <--- has to be admin_organizaions_url
format.json { head :no_content }
end
end
I didn't pay attention to the respond_to block at all.
I'm using the FriendlyId gem and it generates a slug when a record is saved. It works great.
I can route to the slug like: places/house-of-dead, perfect. But I'm having a problem when it comes to testing.
I'm using FactoryGirl to stub the parameters that I'm going to create, but I don't know how to get that generated slug to assert if my route redirects to it, my test is below and (???? should be the generated slug).:
context '#create' do
it 'should redirect to place after created' do
place = FactoryGirl.attributes_for(:place)
post :create, { place: FactoryGirl.attributes_for(:place) }
response.should redirect_to places_path(slug: ????)
end
end
My controller is simple:
def create
#place = Place.new(params[:place])
# setting up who owns this place
#place.user = current_user
if #place.save
flash[:success] = "Place created."
redirect_to #place
else
render :new
end
end
As you can see, I use redirect_to #place and it redirects me to `place/:slug', but when it comes to testing that redirect, how do I do it?
Also, is my testing way right/good?
Thanks.
You almost answered yourself:
response.should redirect_to places_path(assigns(:place))
or if you need to use the slug:
response.should redirect_to places_path(slug: assigns(:place).slug)
See https://github.com/rspec/rspec-rails#assigns for more on assigns().
And this line
place = FactoryGirl.attributes_for(:place)
is redundant, as you never use the place variable in the rest of your test.
So, I've been beating my head against this for a while and just can't make any progress.
I have the following controller action:
def create
#job = Job.new(params[:job])
respond_to do |format|
if #job.save
flash[:notice] = "The Job is ready to be configured"
format.html { redirect_to setup_job_path(#job.id) }
format.json { head :ok }
else
format.html { redirect_to new_job_path, notice: 'There was an error creating the job.' }
format.json { render json: #job.errors, status: :unprocessable_entity }
end
end
end
I am trying to test this action. Here is my test for the redirect on successful creation.
it "redirects to the Job setup" do
job.stub(:id=).with(BSON::ObjectId.new).and_return(job)
job.stub(:save)
post :create
response.should redirect_to(setup_job_path(job.id))
end
job is defined for the whole suite here:
let (:job) { mock_model(Job).as_null_object }
I keep getting the following error:
2) JobsController POST create when the job saves successfully redirects to the Job setup
Failure/Error: response.should redirect_to(setup_job_path(job.id))
Expected response to be a redirect to <http://test.host/jobs/1005/setup> but was a redirect to <http://test.host/jobs/4ea58505d7beba436f000006/setup>
I've tried a few different things but no matter what I try I can't seem to get the proper object ID in my test.
If you stub the :id= you are creating a very weak test. In fact, unless you are super-confident about the Mongoid internals, chances are your tests will break if Mongoid changes the way it generates id. And in fact, it doesn't work.
Also, keep in mind you create a job variable, but you are not passing this variable inside the controller. It means, the :create action will initialize its own job instance at
#job = Job.new(params[:job])
and it will completely ignore your job.
I suggest you to use assigns.
it "redirects to the Job setup" do
post :create
response.should redirect_to(setup_job_path(assigns(:job)))
end