How to test database failure with Minitest? - ruby-on-rails

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

Related

RSpec: Assert successful create request without assigns method

I have this code in a create method inside a Rails controller:
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
To test this code, I have this expectation in an RSpec file:
expect(response).to redirect_to(assigns(:product))
Using assigns is deprecated/has been moved to a gem and frankly I don't care if #product or #my_product has been created in the controller. Actually I just want to know if I have been redirected to /products/<some-id>. Is there a (recommended) way to do so?
If you want render new you you'll need add gem 'rails-controller-testing' to your Gemfile.
After read your comments i guess your action #create is look like that:
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
You could do a test like that:
describe 'POST /products' do
context 'when everithing is ok' do
it 'returns the product' do
post products_url, params: { product: { description: 'lorem ipsum', title: 'lorem ipsum' } }
expect(response).to redirect_to(product_url(Product.last))
end
end
context 'when something worong' do
it 'redirect to new' do
post products_url, params: { product: { description: 'lorem ipsum' } }
expect(response).to render_template(:new)
end
end
end
This GitHub Issue explains why assigns is deprecated
Testing what instance variables are set by your controller is a bad idea. That's grossly overstepping the boundaries of what the test should know about. You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea.
you can test the response status code using have_http_status matcher
expect(response).to have_http_status(:success)

Rails: No route matches [POST] nested resources

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.

rails controller test failing non-deterministicly wrt state leak (I think)

I have a standard rest controller (with load_and_authorize_resource) for which I have the following strong params allowed:
def subscription_params
params.require(:subscription).permit(:email,:confirmed)
end
update action:
def update
respond_to do |format|
if #subscription.update(subscription_params)
format.html { redirect_to #subscription, notice: 'Subscription was successfully updated.' }
format.json { render :show, status: :ok, location: #subscription }
else
format.html { render :edit }
format.json { render json: #subscription.errors, status: :unprocessable_entity }
end
end
end
In my test I have:
test "registered can edit confirmed" do
u = users(:registered)
sign_in u
#subscription = u.subscription
new_value = !#subscription.confirmed
patch :update, id: #subscription, subscription: { confirmed: new_value, email: #subscription.email, token: #subscription.token }
assert_response :redirect
assert_equal new_value, u.subscription.confirmed
assert_redirected_to subscription_path(#subscription)
assert_includes flash[:notice], "Subscription was successfully updated."
end
which fails non-deterministically (the confirmed field isn't updated). I don't know enough about rails' ecosystem to know where the problem is. I'm using devise and cancancan.
If I remove config.active_support.test_order = :random the test fails every time. And If I run the tests that fail on their own they always pass. Which lead me to believe that state is leaking between tests and causing issues but I can't figure out what.
Turns out I needed to call #subscription.reload.

post functional test failing

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

ID problems with Mongoid and RSpec

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

Resources