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
Related
In a system spec, I'm trying to test the correct handling of a database timeout. When that happens a new TinyTds::Error is raised.
Here my controller (EMData handles the DB connection)
class Json::ChartController < ApplicationController
rescue_from TinyTds::Error, with: :connection_timed_out
def index
data = EMData.call(params)
respond_to do |format|
format.json { render json: data }
end
end
def connection_timed_out(_error)
format.json { head :request_timeout }
end
end
Here my spec
context 'when the SQL Server connection times out' do
let(:data_class) { class_spy('EMData').as_stubbed_const }
it 'a feedback message is displayed' do
allow(data_class).to receive(:call).and_raise(TinyTds::Error.new('message'))
...
SUBMIT FORM VIA JS
...
expect(page).to have_content("Some Content")
end
The spec seems pretty straightforward to me. However, when I run it , I get
Rack app error handling request { GET /json/chart/ }
/app/controllers/json/chart_controller.rb:24:in `format' ....
Failure/Error: format.json { head :request_timeout }
ArgumentError:
too few arguments
Am I misisng anything here?
You're missing the respond_to do |format| in connection_timed_out(_error). It should be like:
def connection_timed_out(_error)
respond_to do |format|
format.json { head :request_timeout }
end
end
In the routes.rb I have this nested resource
# OBSERVATIVE SESSIONS
resources :observative_sessions do
# OBSERVATIONS
resources :observations
end
In observations_controller.rb
def new
#observative_session = ObservativeSession.find(params[:observative_session_id])
#observation = Observation.new
#observation.observative_session_id = #observative_session.id
end
def create
#observative_session = ObservativeSession.find(params[:observative_session_id])
#observation = #observative_session.observations.build(observation_params)
#observation.user_id = current_user.id
respond_to do |format|
if #observation.save
format.html { redirect_to [#observative_session, #observation], notice: 'Observation was successfully created.' }
format.json { render :show, status: :created, location: #observation }
else
format.html { render :new }
format.json { render json: #observation.errors, status: :unprocessable_entity }
end
end
end
And in observations_controller_test.rb I set up both observation and observative session. The test of new works just fine.
class ObservationsControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
#observative_session = observative_sessions(:one)
#observation = observations(:two)
sign_in users(:admin_user)
end
test "should get new" do
get new_observative_session_observation_path(#observative_session)
assert_response :success
end
test "should create observation" do
assert_difference('Observation.count') do
post observative_session_observation_path(#observative_session, #observation), params: { observation: { start_time: #observation.start_time, description: #observation.description, rating: #observation.rating, notes: #observation.notes, celestial_body_name: #observation.celestial_body_name, telescope_name: #observation.telescope_name, binocular_name: #observation.binocular_name, eyepiece_name: #observation.eyepiece_name, filter_name: #observation.filter_name, user_id: #observation.user_id, observative_session_id: #observation.observative_session_id }}
end
But this is the error I get in the creation test
test_should_create_observation
ActionController::RoutingError: No route matches [POST] "/observative_sessions/980190962/observations/298486374"
I can't understand what I'm doing wrong.
Thanks for your help.
When you say POST observation_session_observation_path(#observation_session, #observation) you are telling it to post to a url the has both :observation_session_id and an :id in the params, where the id is that of #obseravtion. However, POST paths for create actions don’t take that last id param (ostensibly you are creating a new record with that action).
Try dropping #observation from your path helper (and make sure you are using the correct create path: observation_session_observations_path(#observation_session).
You can do rake routes to see your routes in your terminal, or localhost:3000/rails/info/routes to see it in the browser.
I also see in your new action you are assigning the observation_session_id manually. I recommend you either do what you do later and call #obervation_session.observations.build, or Observation.new(observation_session: #observation_session). You should avoid setting ids like that.
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.
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.