I am using Minitest and have a controller like this:
def update
authorize #flag
respond_to do |format|
if #flag.update(flag_params)
format.html { redirect_to flags_path , success: "#{#flag.name} was successfully updated." }
format.js
else
format.html { render :edit }
# format.js
end
end
end
I have a JS remote select that triggers a form submit and then returns a DataTable update. Works fine in development etc. I commented out the format for the else to force a no format error if the update fails.
Here is my test:
test "should assign flag" do
assert_changes 'flags(:one).assigned_id' do
patch flag_url flags(:one), format: :js, params: { flag: { assigned_id: users(:operator).id } }
end
end
The asssigned_id is nil for flags(:one) and all the patch does it update that column to the id from a user fixture. Yet my test fails:
FAIL FlagsControllerTest#test_should_assign_flag (44.56s)
"flags(:one).assigned_id" didn't change
test/controllers/flags_controller_test.rb:38:in `block in <class:FlagsControllerTest>'
Yet this passes:
patch flag_url #flag, format: :js, params: { flag: { assigned_id: users(:operator).id } }
assert_response :success
Also this fails:
assert flags(:one).assigned_id_changed?
I have double checked my tests / code and don't think I am missing anything here.
Have I missed something obvious or perhaps I am not applying the assertions correctly?
UPDATED INFO FOR ANSWER
Here is my updated model test:
require 'test_helper'
class FlagTest < ActiveSupport::TestCase
fixtures :flags
let(:flag_one) { flags(:one) }
end
and controller test:
require 'test_helper'
class FlagsControllerTest < ActionDispatch::IntegrationTest
test "should assign flag" do
assert_changes 'flag_one.assigned_id' do
patch flag_url flag_one, format: :js, params: { flag: { assigned_id: users(:operator).id } }
end
end
Think the issue is that when you call the fixture directly in code like so...
assert_changes 'flags(:one).assigned_id' do
patch flag_url flags(:one), format: :js, params: { flag: { assigned_id: users(:operator).id } }
end
..the test reloads the instance from the fixture and of course it is unchanged. I've never written a test with a fixture reference inline. What I usually do is set up a let using the value from the fixture...
class FlagTest < ActiveSupport::TestCase
fixtures :flags
let(:a_flag){flags(:one)}
and then use that in my tests.
test "should assign flag" do
assert_changes 'a_flag.assigned_id' do
patch flag_url a_flag, format: :js, params: { flag: { assigned_id: users(:operator).id } }
end
end
Saves some typing if you have a number of tests, and should fix your issue.
Related
I want to test show action in my Shipment controller. To do so I've prepared fairly simple specs:
RSpec.describe ShipmentsController, type: :controller do
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
ShimpentsController.rb
class ShipmentsController < ApplicationController
before_action :set_product
attr_reader :shipment
def show
#shipment = Shipment.find(params[:id])
#items = shipment&.grouped_shipment_items
end
private
def set_product
#product = Product.find(params[:product_id])
end
end
When I use postman everything went well - it returns expected json but in the RSpec test I'm getting:
response.body
=> ""
I think you need to add render_views in your controller spec file.
RSpec.describe ShipmentsController, type: :controller do
render_views
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
Reference: https://rubyinrails.com/2019/04/11/rails-test-jbuilder-json-response-with-rspec/
I think you are not making a request for JSON response with rspec. You can check by putting a breakpoint in your controller action, then checking
request.format.json?
In order to ask for JSON response from an rspec test, you should add as: :json to the end of the request. The request should look like this:
get :show, params: params, as: :json
I'm biginer.
I studied Rspec.
I made an implementation that allowed me to do good on my posts.
But on the browser I do the expected move, but the test does not pass.
The destroy action goes through the test, but the create action does not pass the test.
My error is
Failure/Error: expect { post :create, format: :js, params: { post_id: post1.id, id: like.id } }.to change(Like, :count).by(1)
expected #count to have changed by 1, but was changed by 0
My code is
require 'rails_helper'
RSpec.describe LikesController, type: :controller do
let!(:user) { create(:user) }
let!(:post1) { create(:post, user: user) }
let!(:like) { create(:like, user_id: user.id, post_id: post1.id) }
describe "#create" do
before do
sign_in user
end
it "response Ajex" do
post :create, format: :js, params: { post_id: post1.id, id: like.id }
expect(response.content_type).to eq 'text/javascript'
end
it "success like function" do
expect { post :create, format: :js, params: { post_id: post1.id, id: like.id } }.to change(Like, :count).by(1)
end
end
describe "#destroy" do
before do
sign_in user
end
it "response Ajex" do
delete :destroy, format: :js, params: { post_id: post1.id, user_id: user.id, id: like.id }
expect(response.content_type).to eq 'text/javascript'
end
it "delete like function" do
expect { delete :destroy, format: :js, params: { post_id: post1.id, user_id: user.id, id: like.id } }.to change(Like, :count).by(-1)
end
end
end
likes_controller.rb
class LikesController < ApplicationController
def create
#like =
current_user.likes.find_or_create_by(post_id:params[:post_id])
#likes = Like.where(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
def destroy
like = current_user.likes.find_by(post_id: params[:post_id])
like.destroy
#likes = Like.where(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
end
I cannot solove this problem.
Please teach me a hint.
You've got an error in your code somewhere, most likely, which is why the Like count fails to increment. First, I'd try and figure out why it isn't incrementing. Since you asked for a hint, here's one way you can split out the "success like function" block:
context "valid" do
before do
post :create, format: :js, params: { post_id: post1.id, id: like.id }
end
it "success" do
# You can inject a binding.pry here if needed
expect(response.status).to eq(200)
end
it "response" do
# You can inject a `binding.pry` here if needed
# You can also inspect the `response.body` with puts if needed
expect(JSON.parse(response.body)).to include(
# You would modify this to match the shape of your response
post: a_hash_including(
like: like.id
)
)
end
end
You'll want to install pry-rails and pry-byebug gems (for inspecting).
The reason behind splitting them up is it makes it easier to determine the issue (you can have a valid response code but not the expected result, for example). This comes with some caveats (it will make for slower tests) but in this example it will make it easier to determine why your post is failing.
The snippet above should help you debug the error; once you fix it you can revert back to your previous method of checking.
I have a situation to render a HTML template outside of controller(A class under service/lib directory) and I am rendering the template using the below code.
class SomeClass
def some_method
#template = ApplicationController.render(
template: 'template',
layout: mailer_template,
)
end
end
Is there any ways to test if the rendered template is the expected one and whether render happened during that method call?
EDIT
class BatchSendingService < AbstractController::Base
require 'abstract_controller'
include AbstractController::Rendering
include AbstractController::AssetPaths
include AbstractController::Helpers
include Rails.application.routes.url_helpers
include ActionView::Rendering
include ActionView::ViewPaths
include ActionView::Layouts
self.view_paths = "app/views"
def send_batch_email(mail, domain)
#project = mail.project
#client = Mailgun::Client.new ENV['MAILGUN_API_KEY']
batch_message = Mailgun::BatchMessage.new(#client, domain)
batch_message.from(from_data)
mailer_layout = get_mailer_layout(mail.layout)
mail_html = render(
template: 'send_batch_email',
layout: mailer_layout
)
batch_message.body_html(mail_html.to_s)
batch_message.add_recipient(:to, recipient_email, {})
response = batch_message.finalize
end
EDIT
obj= BatchSendingService.new
allow(obj).to receive(:render)
BatchSendingService.send_batch_email(mail, domain)
expect(obj) .to have_received(:render)
.with({ template: "template", layout: "layout" })
By using the class where the instance method is called, the error is gone.
ActionController.render is a well tested method. The Rails Core Team saw to that. There's no need to test that it does what it says it does.
Rather, what you want to do is to make sure you called ActionController.render with the right parameters, using mock objects, like this:
describe SomeClass do
subject(:some_class) { described_class.new }
describe '#some_method' do
let(:template) { 'template' }
let(:layout) { 'mailer_template' }
before do
allow(ActionController).to receive(:render)
some_class.some_method
end
it 'renders the correct template' do
expect(ActionController)
.to have_received(:render)
.with({ template: template, layout: layout })
end
end
end
EDIT
Given the edited post, here's how I would approach the test. Note that not all of the code in your send_batch_email method is visible in your edit. So, YMMV:
describe BatchSendingService do
subject(:batch_sending_service) { described_class.new }
describe '#send_batch_email' do
subject(:send_batch_email) do
batch_sending_service.send_batch_email(email, domain)
end
let(:email) { 'email' }
let(:domain) { 'domain' }
let(:batch_message) do
instance_double(
Mailgun::BatchMessage,
from: true,
body_html: true,
add_recipient, true,
finalize: true
)
end
let(:template) { 'send_batch_template' }
let(:layout) { 'layout' }
before do
allow(Mailgun::Client).to receive(:new)
allow(Mailgun::BatchMessage)
.to receive(:new)
.and_return(batch_message)
allow(batch_sending_service)
.to receive(:render)
send_batch_email
end
it 'renders the correct template' do
expect(batch_sending_service)
.to have_received(:render)
.with(template, layout)
end
end
end
I have a create action that calls an ActiveJob if the record is successfully saved.
def create
#object = Object.new(importer_params)
respond_to do |format|
if #object.save
MyJob.perform_later( #object.id )
format.html { redirect_to #object, notice: t('.notice') }
else
format.html { render :new }
end
end
end
I want to test that the Job is correctly called in a controller spec.
describe "POST #create" do
it {
expect {
post :create, { object: valid_attributes }
}.to change(Object, :count).by(1)
}
it {
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
}
end
But I get
Failure/Error:
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
expected to enqueue exactly 1 jobs, but enqueued 0
The first test is passing, so I know the Object is saved successfully. What is the correct way to test that an ActiveJob is enqueued?
If you need to check that your job has been enqueued several times, you can now do this:
expect {
3.times { HelloJob.perform_later }
}.to have_enqueued_job(HelloJob).at_least(2).times
I've always looked at the size of ActiveJob::Base.queue_adapter.enqueued_jobs to test if a job was called. giving the code
it 'does something' do
expect {
post :create, { object: valid_attributes }
}.to change {
ActiveJob::Base.queue_adapter.enqueued_jobs.count
}.by 1
end
You should make sure that you are setting the enqueued_jobs to an empty array after each spec to avoid any unexpected behaviour. You can do this in the spec/rails_helper.rb
In official docs here is have_enqueued_job matcher
The have_enqueued_job (also aliased as enqueue_job) matcher is used to check if given ActiveJob job was enqueued.
https://relishapp.com/rspec/rspec-rails/docs/matchers/have-enqueued-job-matcher
I have a create action that calls an ActiveJob if the record is successfully saved.
def create
#object = Object.new(importer_params)
respond_to do |format|
if #object.save
MyJob.perform_later( #object.id )
format.html { redirect_to #object, notice: t('.notice') }
else
format.html { render :new }
end
end
end
I want to test that the Job is correctly called in a controller spec.
describe "POST #create" do
it {
expect {
post :create, { object: valid_attributes }
}.to change(Object, :count).by(1)
}
it {
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
}
end
But I get
Failure/Error:
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
expected to enqueue exactly 1 jobs, but enqueued 0
The first test is passing, so I know the Object is saved successfully. What is the correct way to test that an ActiveJob is enqueued?
If you need to check that your job has been enqueued several times, you can now do this:
expect {
3.times { HelloJob.perform_later }
}.to have_enqueued_job(HelloJob).at_least(2).times
I've always looked at the size of ActiveJob::Base.queue_adapter.enqueued_jobs to test if a job was called. giving the code
it 'does something' do
expect {
post :create, { object: valid_attributes }
}.to change {
ActiveJob::Base.queue_adapter.enqueued_jobs.count
}.by 1
end
You should make sure that you are setting the enqueued_jobs to an empty array after each spec to avoid any unexpected behaviour. You can do this in the spec/rails_helper.rb
In official docs here is have_enqueued_job matcher
The have_enqueued_job (also aliased as enqueue_job) matcher is used to check if given ActiveJob job was enqueued.
https://relishapp.com/rspec/rspec-rails/docs/matchers/have-enqueued-job-matcher