I do not yet have a good grasp on mocking and stubbing. How would I go about stubbing the aodc.success? call to return false? The AodcWrapper::Order.create is not complete and will always return success today. But I need to simulate the fail case.
Here is the create method
def create
#user = current_user
#order = Order.new(order_params)
if #order.valid?
aodc = AodcWrapper::Order.create(#order)
if aodc.success?
# pending... Capture the authorization
#order.save
UserMailer.order_received_email(#user).deliver_later
StaffMailer.order_received_email(#user).deliver_later
render json: #order, serializer: Api::V1::OrderSerializer
else
render json: { status: :failure, error: aodc.error_message }
end
else
render json: { status: :failure, error: #order.errors.full_messages }
end
end
And here is the test
context "sad path 1: can not connect to the aodc" do
before do
#user = FactoryGirl.create(:user)
#order = FactoryGirl.attributes_for(:shippable_order, user: #user)
sign_in #user
post :create, user_id: #user.id, order: #order
end
it "reponds with a could not connect message" do
parsed_response = JSON.parse(response.body)
expect(parsed_response[:error]).not_to be_nil
end
end
Side quest. Any recommendations on resources to explore so that I can not suck at mocks and stubs?
So I was going about that completely wrong. The AodcWrapper was making an API call with Httparty.
The path I took to solve this was
Use VCR to record the API interaction
The test failed because the response was successful
Modify the VCR cassette yml and change the success response to my desired (not yet implemented) error message
Rerun the test and all is well.
Related
I have a delete function in one of my controllers like below:
before_action :set_form, only: [:show, :edit, :update, :destroy]
def destroy
if #form.destroy
render json: #form, status: :ok
else
render json: #form.errors, status: :not_found
end
end
private
def set_form
#form = Form.find(params[:id])
end
end
I have two questions:
1) I'm returning 404 when delete is not correctly performed. Is this reasonable status code? I looked into all 4XX status code and this was making most sense.
2) I am not sure how I am gonna write a test for render json: #form.errors, status: :not_found.
Below is what I tried:
context "Cannot delete the model" do
it "responds successfully with an HTTP 404 status code" do
delete :destroy, id: 100000
expect(response).to have_http_status(404)
end
end
The problem is that I get an error saying ActiveRecord::RecordNotFound:Couldn't find Visa with 'id'=10000 instead of delete action actually failing. How do I mock that delete action is failing?
That error is the same error you'll get in development and even production. The line...
#form = Form.find(params[:id])
Will raise an exception if no Form record is found with the delivered ID.
also if the #form is not found and you find a mechanism to continue, the #form.destroy will still not work because there's no method destroy on nil objects, and there's also no method errors on nil objects.
If you insist on handling destroy methods as you describe, you'd need to do something like...
before_action :set_form, only: [:show, :edit, :update] #remove destroy
def destroy
#form = Form.find_by(id: params[:id])
if #form
#form.destroy
render json: #form, status: :ok
else
render json: 'record not found for delete', status: :not_found
end
end
Your set_form method raises that error, you have to catch it there or use find_by_id in destroy.
def set_form
#form = Form.find(params[:id])
rescue ActiveRecord::RecordNotFound
end
This way #form will be nil in your if.
As for HTTP status code, you might consider using 422.
Rails will raise an ActiveRecord::RecordNotFound when you call .find on a record with an invalid id. This is a very desirable behaviour since it provides a nice catch-all for when a resource cannot be found.
The default is to send a 404 - not found header. For the HTML request type it also sends a default error page.
Rather the issue is how you are testing it.
If you want to test that rails raises the error:
context "Cannot delete the model" do
it "raises an error when the id is not valid" do
bypass_rescue
expect { delete :destroy, id: 100000 }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to have_http_status(404)
end
end
However if you want to test that deleting the model fails for any other reason (record is locked or user is not authorized for example) you should set up a record in the database with fixtures or factories and provide a valid id - you should also provide the correct response code (422 or 401).
As to mocking the delete failure you can use allow_any_instance_of(SomeModel).to receive(:destroy).and_return(false).
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 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.
I am trying to create a functional test that tests the create method in one of my controllers. For the life of me, I cannot figure out why this is failing. I am getting one failure, and zero errors:
1) Failure:
test_should_create_order(OrdersControllerTest) [/Users/user/rails_work/depot/test/functional/orders_controller_test.rb:38]:
"Order.count" didn't change by 1.
<3> expected but was
<2>.
So, Im pretty sure this means that my functionals test was unable to make an Order. Here is my test:
setup do
#order = orders(:one)
end
test "should create order" do
assert_difference('Order.count') do
post :create, order: #order.attributes.slice(Order.accessible_attributes)
end
assert_redirected_to store_url
end
my orders fixture:
one:
name: Dave Thomas
address: MyText
email: dave#example.org
pay_type: Check
and my Order#create controller:
def create
#order = Order.new(params[:order])
#order.add_line_items_from_cart(current_cart)
respond_to do |format|
if #order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to store_url, notice: 'Thank you for your order' }
format.json { render json: #order, status: :created, location: #order }
else
#cart = current_cart
format.html { render action: "new" }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
Now, if I change the setup method in my functional test to:
#order = Order.create(orders(:one))
Instead of:
#order = orders(:one)
The failure disappears, but I get about 8 of these errors:
NoMethodError: undefined method `stringify_keys' for #<Order:0x007f8c62dbb960>
If anyone can help me fix this functional test, I would more than appreciate it. Any and all input is welcome.
Bottom line: assign each order attribute individually.
I'm not familiar with the 'mass assignment' vulnerability (new to Rails), but here (Pragmatic Forums) is a case of someone having difficulty with that specific test because of it.
Try spelling out each attribute of the order individually. Instead of
post :create, order: #order.attributes.slice(Order.accessible_attributes)
use
post :create, order: {
address: #order.address,
email: #order.email,
name: #order.name,
pay_type: #order.pay_type
}
The test as a whole will be this:
test "should create order" do
assert_difference('Order.count') do
post :create, order: { address: #order.address, email: #order.email, name: #order.name, pay_type: #order.pay_type }
end
assert_redirected_to store_path
end
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