Testing update action in Minitest - ruby-on-rails

I have test for create:
test "should create article" do
assert_difference('Article.count') do
post :create, article: {title: "Test", body: "Test article."}
end
assert_redirected_to article_path(assigns(:article))
end
I want to do something like this for update action.
My update action looks like:
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
I am thinking about something like:
test "should update article" do
patch :update, article {title: "Updated", body: "Updated article."}
end
But I the problems: how to check is my article is updated in Minitest? And how to find item I am going to updated? In fixtures I have two articles.

You should be able to assign one of your fixture articles to a variable and run assertions on the article post-update, something like this (I haven't tested this code, it's just to illustrate the test structure):
test "should update article" do
article = articles(:article_fixture_name)
updated_title = "Updated"
updated_body = "Updated article."
patch :update, article: { id: article.id, title: updated_title, body: updated_body }
assert_equal updated_title, article.title
assert_equal updated_body, article.body
end
You may want to initialize article as an instance variable in your setup method and set it to nil in your teardown method, or however you're managing setup/teardown to make sure your starting state stays consistent from test to test.

Related

Rspec test not hitting controller update

I am trying to write a simple test to validate updating an Employee and while it works in practice I wanted to write the test anyway.
RSpec.describe EmployeesController, type: :controller do
before(:each) do
admin_user = FactoryBot.create(
:user,
user_type: 1,
email: "admin#admin.com",
password: "oeifhoi2345tf",
password_confirmation: "oeifhoi2345tf"
)
login_as(admin_user)
#employee = create(:employee)
end
it 'expects employee values to update following update' do
p #employee
put :update, params: {
id: #employee[:id],
employee: { name: "New Name" }
}
#employee.reload
p #employee
expect(#employee.name).to eq("New Name")
end
end
The test fails and #employee.name remains unchanged. I have a feeling the update it not even occurring because I added a print line to my controller and I do not see it in my logs:
def update
p "IN UPDATE"
if #employee.update(employee_params)
redirect_to edit_employee_path(#employee[:id])
else
render :edit
end
end
Is there something I am missing in my put call?

Topic.count didn't change by 1

I am testing the create method of my topics controller and keep getting this error:
"Topic.count" didn't change by 1.
Expected: 4
Actual: 3
I have tried a bunch of different solutions from changing the number of attributes that I send in the params to changing what the attributes themselves are, but nothing seems to be working. Any ideas?
Here is my create method:
def create
topic = current_user.topics.create(topic_params)
if topic.save
redirect_to topic
unless current_user.reload.spammer?
AdminMailer.topic_created(topic).deliver_now
end
else
redirect_to topics_path, flash: 'Failed to save this question. Please try again or contact email#domain.com'
end
end
Here is my test:
test "should create topic" do
assert_difference 'Topic.count' do
post topics_url, params: {topic: {title: 'this is a topic', message_attributes: {text: 'this is the question'}, user_id: #user.id, bound_id: #bound.id}}
end
assert_redirected_to topics_url(Topic.last)
end

Rspec method is being called 2X, but can't find second time

Here is my controller spec
before do
#order = Order.new
end
it "should call find & assign_attributes & test delivery_start methods" do
Order.should_receive(:find).with("1").and_return(#order)
Order.any_instance.should_receive(:assign_attributes).with({"id"=>"1", "cancel_reason" => "random"}).and_return(#order)
Order.any_instance.should_receive(:delivery_start).and_return(Time.now)
post :cancel, order: {id:1, cancel_reason:"random"}
end
The failure is this:
Failure/Error: Unable to find matching line from backtrace
(#<Order:0x007fdcb03836e8>).delivery_start(any args)
expected: 1 time with any arguments
received: 2 times with any arguments
# this backtrace line is ignored
But I'm not sure why delivery_start is being called twice based on this controller action:
def cancel
#order = Order.find(cancel_params[:id])
#order.assign_attributes(cancel_params)
if (#order.delivery_start - Time.now) > 24.hours
if refund
#order.save
flash[:success] = "Your order has been successfully cancelled & refunded"
redirect_to root_path
else
flash[:danger] = "Sorry we could not process your cancellation, please try again"
render nothing: true
end
else
#order.save
flash[:success] = "Your order has been successfully cancelled"
redirect_to root_path
end
end
I would suggest you test the behavior and not the implementation. While there are cases where you would want to stub out the database doing it in a controller spec is not a great idea since you are testing the integration between your controllers and the model layer.
In addition your test is only really testing how your controller does its job - not that its actually being done.
describe SomeController, type: :controller do
let(:order){ Order.create } # use let not ivars.
describe '#cancel' do
let(:valid_params) do
{ order: {id: '123', cancel_reason: "random"} }
end
context 'when refundable' do
before { post :cancel, params }
it 'cancels the order' do
expect(order.reload.cancel_reason).to eq "random"
# although you should have a model method so you can do this:
# expect(order.cancelled?).to be_truthy
end
it 'redirects and notifies the user' do
expect(response).to redirect_to root_path
expect(flash[:success]).to eq 'Your order has been successfully cancelled & refunded'
end
end
end
end
I would suggest more expectations and returning true or false depending on your use. Consider the following changes
class SomeController < ApplicationController
def cancel
...
if refundable?
...
end
end
private
def refundable?
(#order.delivery_start - Time.now) > 24.hours
end
end
# spec/controllers/some_controller_spec.rb
describe SomeController, type: :controller do
describe '#cancel' do
context 'when refundable' do
it 'cancels and refunds order' do
order = double(:order)
params = order: {id: '123', cancel_reason: "random"}
expect(Order).to receive(:find).with('123').and_return(order)
expect(order).to receive(:assign_attributes).with(params[:order]).and_return(order)
expect(controller).to receive(:refundable?).and_return(true)
expect(controller).to receive(:refund).and_return(true)
expect(order).to receive(:save).and_return(true)
post :cancel, params
expect(response).to redirect_to '/your_root_path'
expect(session[:flash]['flashes']).to eq({'success'=>'Your order has been successfully cancelled & refunded'})
expect(assigns(:order)).to eq order
end
end
end
end
Sorry, this is a very unsatisfactory answer, but I restarted my computer and the spec passed...
One thing that has been a nuisance for me before is that I've forgotten to save the code, i.e., the old version of the code the test is running against called delivery_start twice. But in this case, I definitely checked that I had saved. I have no idea why a restart fixed it...

Table count does not increase after save returns true for nested resource rspec test

I have a 'Mastertag' model as a nested resource for 'Project' with a create action as:
def create
#mastertag = #project.mastertags.build(params[:mastertag])
if #mastertag.save
redirect_to project_mastertags_path, notice: 'Mastertag was successfully created.'
else
render action: "new"
end
end
where #project is initialized in a before filter method.
I have an rspec test as:
describe "POST create" do
context "with valid params" do
it "creates a new Mastertag" do
expect {
post :create, { project_id: #project.id, mastertag: FactoryGirl.attributes_for(:mastertag_without_project) }
}.to change(Mastertag, :count).by(1)
end
end
When I run the test, the #mastertag.save method returns true however the count still remains the same. The test hence fails. This looks pretty strange. Where am I going wrong?
As I was using Mongoid and 'Mastertags' was embedded into Project, there is no separate collection for Mastertags.
I had to change the code to :
describe "POST create" do
context "with valid params" do
it "creates a new Mastertag" do
expect {
post :create, { project_id: #project.id, mastertag: FactoryGirl.attributes_for(:mastertag_without_project) }
}.to change {#project.reload.mastertags.count}.by(1)
end
end
I got help from this Stackoverflow question : RSpec/Mongoid: Expect to change count on embedded models
Check your project_mastertags_path and make sure the redirect after if #mastertag.save works.
also, try replacing if #mastertag.save with if #project.save in your create method.

Force controller to use current_user with mocking

I am trying to specify in my RSpec tests that my controller should use current_user.projects.find() instead of Project.find() I am using the Mocha mocking framework and was trying something like this:
controller.current_user.projects.expects(:find).returns(#project)
I have already mocked out controller.stubs(:current_user).returns(#profile)
This test passes with this even when I use the Project.find() implementation. How can I test that my controller is calling off of the correct object?
Edit (adding additional code):
I have Projects and Tasks, Project have many tasks. This is the show method for displaying a task in a project that is owned by current_user
Action in the controller:
def show
#project = current_user.projects.find_by_id(params[:cardset_id])
if #project.nil?
flash[:notice] = "That project doesn't exist. Try again."
redirect_to(projects_path)
else
#task = #project.tasks.find_by_id(params[:id])
end
end
This is the test that is not checking that the cardsets method was called off the current_user object.
Current Test:
context "with get to show" do
context "with valid project" do
before(:each) do
#project = Factory(:project)
#task = Factory(:task)
#profile = #project.profile
ApplicationController.stubs(:require_user).returns(true)
controller.stubs(:current_user).returns(#profile)
Project.stubs(:find_by_id).returns(#project)
#project.tasks.stubs(:find_by_id).returns(#task)
get :show, :project_id => #project.id, :id => #task.id
end
it "should assign task" do
assigns[:task].should_not be_nil
end
it "should assign project" do
assigns[:project].should_not be_nil
end
end
context "with invalid project" do
before(:each) do
Project.stubs(:find_by_id).returns(nil)
get :show, :project_id => #project.id, :id => #task.id
end
it "should set flash" do
flash[:notice].should match(/doesn't exist/i)
end
it "should redirect" do
response.should redirect_to(cardsets_url)
end
end
end
Based on the little you've told us, I think you need:
#profile.expects(:find).returns(#project)

Resources