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?
Related
I want to test the API request that creates Stripe customers.
Controller
def create
user = User.create(create_params)
stripe_customer = Stripe::Customer.create({
email: create_params[:email],
name: [create_params[:first_name], create_params[:last_name]].join(' ')
})
user.update(stripe_customer_id: stripe_customer.id)
render(json: { user: user }, status: :ok)
end
private
def create_params
params.permit(
:email,
:first_name,
:last_name
)
end
I saw there is stripe-ruby-mock gem but I am not sure how I can use it?
First of all, you should be able to trust that Stipe's gem will do the right thing when you call it. So, there's no need to test that their create method returns the correct thing. So, all you need to test is that you are calling their API correctly. All you need to do this is an RSpec mock.
Since your code is in a controller, you'll either need to use a controller spec, or you'll need to refactor your code into another class. My preference is the refactor. So, that's what I'll show here.
Controller
Refactored to move business logic into a command class.
def create
user = CreateUser.new(create_params).perform
render(json: { user: user }, status: :ok)
end
CreateUser command class
Refactored a bit to only make one call to the database.
class CreateUser
attr_reader :params
def initialize(params)
#params = params
end
def perform
stripe_customer = Stripe::Customer.create({
email: params[:email],
name: [params[:first_name], params[:last_name]].join(' ')
})
User.create(params.merge(stripe_customer_id: stripe_customer.id))
end
end
Spec file for CreateUser command class
describe CreateUser do
subject(:create_user) { described_class.new(params)
let(:params) do
email: 'some#email.address',
first_name: 'first',
last_name: 'last'
end
describe 'perform' do
let(:stripe_customer_id) { 123 }
before do
allow(Stripe::Customer).to receive(:create).and_return(stripe_customer_id)
allow(User).to receive(:create)
end
it 'creates a Stripe customer' do
create_user.perform
expect(Stripe::Customer).to have_received(:create).with(
email: 'some#email.address',
name: 'first last'
)
end
it 'creates a user with a stripe customer id' do
create_user.perform
expect(User).to have_received(:create).with(
email: 'some#email.address',
first_name: 'first',
last_name: 'last',
stripe_customer_id: 123
)
end
end
end
I am testing a controller method for creating new orders (e-commerce-like app). If user is present in the system, he should be redirected to new_user_session_path, else to new_order_path. Simple as that.
This is my orders_controller.rb
def new
if !User.where(phone: params[:phone]).blank? && !user_signed_in?
redirect_to new_user_session_path()
flash[:info] = "Already present"
else
#order = Order.new
#menu = Menu.find(params[:menu_id])
#menu_price = #menu.calculate_price(#menu, params)
end
end
In my app, I need the calculate_price method to be called, because it calculates the overall price given the params. But in my test, I just want to ensure, that the redirect is correct.
Right now I'm getting errors like (they are sourced inside the Menu.rb file, since calculate_price is called) :
Front::OrdersController#new redirects user to new order page if user is not present in the system
Failure/Error: menu_price_change = menu_amount.split(",")[1].gsub(" ","").gsub("]",'')
NoMethodError:
undefined method `split' for nil:NilClass
This is my spec file:
require 'rails_helper'
describe Front::OrdersController, type: :controller do
describe '#new' do
# Set up dummy menu
let (:menu) { Menu.create() }
it "redirects user to sign up page if user is present in the system" do
user = User.create(name: "Bob", password: "bobspassword", phone: "+7 (903) 227-8874")
get :new, params: { phone: user.phone }
expect(response).to redirect_to(new_user_session_path(phone: user.phone))
end
it "redirects user to new order page if user is not present in the system" do
non_present_phone = "+7 (903) 227-8874"
get :new, params: { phone: non_present_phone, menu_id: menu.id}
expect(response).to redirect_to(new_order_path)
end
end
end
Of course I could provide all the params, but there is a pretty big amount of them and besides, I just want to test the correct redirect. As far as I know, mocks and subs are useful in this case, when you want to explicitly test the methods. But in my case, I want to - somehow - omit them. How can I ensure that behaviour?
So you want just to test redirects and the errors occured when calculate_price method executes bother you. Why don't you just stub that method? Your spec file might be like this:
require 'rails_helper'
describe Front::OrdersController, type: :controller do
describe '#new' do
# Set up dummy menu
let (:menu) { Menu.create() }
# Check this out
before do
allow_any_instance_of(Menu).to receive(:calculate_price)
# or if you need certain value
allow_any_instance_of(Menu).to receive(:calculate_price).and_return(your_value)
end
it "redirects user to sign up page if user is present in the system" do
user = User.create(name: "Bob", password: "bobspassword", phone: "+7 (903) 227-8874")
get :new, params: { phone: user.phone }
expect(response).to redirect_to(new_user_session_path(phone: user.phone))
end
it "redirects user to new order page if user is not present in the system" do
non_present_phone = "+7 (903) 227-8874"
get :new, params: { phone: non_present_phone, menu_id: menu.id}
expect(response).to redirect_to(new_order_path)
end
end
end
I am new to testing in rails. I am trying to test the update action for my systems controller in my application using rspec.
Here is the code for the test I am running:
describe 'Put update' do
before (:each) do
#system = FactoryGirl.create(:system, name:"WAM", responsible_personnel: "Jeff", criticality: 1, status: "Available")
end
context "valid attributes" do
it "located the requested #system" do
put :update, id: #system, system: FactoryGirl.attributes_for(:system)
assigns(:contact).should eq(#system)
end
end
end
My factory code:
# spec/factories/systems.rb
require 'faker'
FactoryGirl.define do
factory :system do |f|
f.name { Faker::Lorem.word }
f.responsible_personnel { Faker::Name.name}
f.criticality {Faker::Number.between(1,5)}
f.status "Available" || "Unavailable"
end
factory :invalid_system, parent: :system do |f|
f.name nil
end
end
When I run the test i get this error:
expected: #<System id: 285, name: "WAM", responsible_personnel: "Jeff", created_at: "2016-02-25 18:34:43", updated_at: "2016-02-25 18:34:43", criticality: 1, status: 0>
got: nil
(compared using ==)
Here is my systems controller update action
def update
if #system.update(system_params)
flash[:success] = "System successfully updated."
redirect_to edit_system_path
else
format.html { render :edit }
end
end
Your spec is expecting there to be a variable called #contact created in your controller. I'm guessing that's a typo, and your assigns statement should be:
assigns(:system).should eq(#system)
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...
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.