Test ExceptionNotification middleware in rails unit test - ruby-on-rails

I have a service class that I am testing. The service class makes an API call to an external resource. If the resource comes back empty, I would like to raise an exception that sends an email using the ExceptionNotification gem.
This gem operates as middleware and is usually not enabled in the test environment. However, in my service class test, I would like to test that the exception notification goes out as it's very important we are notified that the API request failed.
My understanding of middleware is that they are usually in the context of a request and an app. However, if I enable the ExceptionNotification gem in config/environments/test.rb and run my unit test, the exception notification email is sent out.
So, my question is how can I temporarily turn on and off this middleware for just this test without having an "app" to add it into, so I figure this must be possible without being in a feature spec.
Ok, here's the code I'm working with and started:
class MyService
ThingNotFound = Class.new(Exception)
def self.doit(params)
the_thing = ApiResource.get_the_thing(params)
raise ThingNotFound unless the_thing.present?
return the_thing
rescue ThingNotFound => e
ExceptionNotifier.notify_exception(e, data: {params: params})
end
end
In config/environments/test.rb, here is the ExceptionNotifier middleware code:
config.before_initialize do
MyApp::Application.config.middleware.use ExceptionNotification::Rack
end

Here's a hacky way I came up with. Open to alternatives. Essentially, when you include it in the test.rb, it instantiates the middleware which sets up some class variables so you can use it outside the context of a Rack app, so we just need to reproduce that.
describe 'MyService' do
before { ExceptionNotification::Rack.new(Recognize::Application, email: {:email_prefix => "[PREFIX] ", :sender_address => "whatever#whatever.com", :exception_recipients => %w(noone#noone.com)}) }
after { ExceptionNotifier.class_variable_set("##notifiers", {}) }
it 'sends an exception email when no thing is found' do
ApiResource.stub(get_the_thing: nil)
expect{ MyService.doit({}) }.to change{ActionMailer::Base.deliveries.length}.by(1)
end
end

Related

How to use double to test exception handling in unit testing in ruby on rails?

I want to test my send method logic in my Client class. I have this so far in my client_spec file. How can i raise and error and test that rescue is called and the error is logged in my spec file.
I am new to rspec but I believe i can use a test double for logging instead of calling actual logger.
client_spec.rb
describe Client do
describe '#send' do
let (:subject) {Client.new}
it 'raises and logs the exception'
//how to test raising and logging of the error
end
end
end
client.rb
class Client
include HTTParty
base_uri "https://www.example.com"
format :json
def send
begin
response = HTTParty.get(url)
if response.successful?
response
else
raise 'invalid response'
end
rescue HTTParty::Error => e
logger.warn(e.message)
end
end
end
yes, you can stub both HTTParty and logger, something like
let(:logger) { double('Logger') }
let(:error) { HTTParty::Error.new('foo') }
before do
allow(Logger).to receive(:new) { logger }
end
(Not sure what kind of logger are you using)
and then you can tell HTTParty to raise the kind of error you're expecting, like:
context 'when there is a HTTParty error' do
before do
allow(HTTParty).to receive(:get).and_raise(error)
end
it 'logs the error' do
expect(logger).to receive(:warn).with('foo')
subject.send
end
end
Ok, so, the expect before the send is a way to test if the tested method will "trigger" or perform additional operations, it's a way to say "when I execute foo, I expect that "this" happened". There is a way to declare way around, like:
subject.send
expect(logger).to have_received(:warn).with('foo')
But I think this way is newer than the one I proposed, I just got really used to use the proposed one.
About mocks and stubs, yeah, you don't test them because those objects are out of the scope of the class you're testing on. So, Logger should have its own set of tests, the same for HTTParty, so, you "simulate" their behaviors in order to test your class, that way you'll remove the dependency between the test and other libraries (or classes).
When your unit test is done, then you can move to an integration test, and test (sorry for the redundancy) that the whole stack (or endpoint or controller or "flow") is doing what you're expecting to do.

Test for HTTP status code in some RSpec rails request exampes, but for raised exception in others

In a Rails 4.2.0 application tested with rspec-rails, I provide a JSON web API with a REST-like resource with a mandatory attribute mand_attr.
I'd like to test that this API answers with HTTP code 400 (BAD REQUEST) when that attribute is missing from a POST request. (See second example blow.) My controller tries to cause this HTTP code by throwing an ActionController::ParameterMissing, as illustrated by the first RSpec example below.
In other RSpec examples, I want raised exceptions to be rescued by the examples (if they're expected) or to hit the test runner, so they're displayed to the developer (if the error is unexpected), thus I do not want to remove
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
from config/environments/test.rb.
My plan was to have something like the following in a request spec:
describe 'POST' do
let(:perform_request) { post '/my/api/my_ressource', request_body, request_header }
let(:request_header) { { 'CONTENT_TYPE' => 'application/json' } }
context 'without mandatory attribute' do
let(:request_body) do
{}.to_json
end
it 'raises a ParameterMissing error' do
expect { perform_request }.to raise_error ActionController::ParameterMissing,
'param is missing or the value is empty: mand_attr'
end
context 'in production' do
###############################################################
# How do I make this work without breaking the example above? #
###############################################################
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
# Above matcher provided by api-matchers. Expectation equivalent to
# expect(response.status).to eq 400
end
end
end
# Below are the examples for the happy path.
# They're not relevant to this question, but I thought
# I'd let you see them for context and illustration.
context 'with mandatory attribute' do
let(:request_body) do
{ mand_attr: 'something' }.to_json
end
it 'creates a ressource entry' do
expect { perform_request }.to change(MyRessource, :count).by 1
end
it 'reports that a ressource entry was created (HTTP status 201)' do
perform_request
expect(response).to create_resource
# Above matcher provided by api-matchers. Expectation equivalent to
# expect(response.status).to eq 201
end
end
end
I have found two working and one partially working solutions which I'll post as answers. But I'm not particularly happy with any of them, so if you can come up with something better (or just different), I'd like to see your approach! Also, if a request spec is the wrong type of spec to test this, I'd like to know so.
I foresee the question
Why are you testing the Rails framework instead of just your Rails application? The Rails framework has tests of its own!
so let me answer that pre-emptively: I feel I'm not testing the framework itself here, but whether I'm using the framework correctly. My controller doesn't inherit from ActionController::Base but from ActionController::API and I didn't know whether ActionController::API uses ActionDispatch::ExceptionWrapper by default or whether I first would have had to tell my controller to do so somehow.
You'd want to use RSpec filters for that. If you do it this way, the modification to Rails.application.config.action_dispatch.show_exceptions will be local to the example and not interfere with your other tests:
# This configure block can be moved into a spec helper
RSpec.configure do |config|
config.before(:example, exceptions: :catch) do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions) { true }
end
end
RSpec.describe 'POST' do
let(:perform_request) { post '/my/api/my_ressource', request_body }
context 'without mandatory attribute' do
let(:request_body) do
{}.to_json
end
it 'raises a ParameterMissing error' do
expect { perform_request }.to raise_error ActionController::ParameterMissing
end
context 'in production', exceptions: :catch do
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
end
end
The exceptions: :catch is "arbitrary metadata" in RSpec speak, I chose the naming here for readability.
Returning nil from a partially mocked application config with
context 'in production' do
before do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions)
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
or more explicitly with
context 'in production' do
before do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions).and_return nil
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
would work if that was the only example being run. But if it was, we could just as well drop the setting from config/environments/test.rb, so this is a bit moot. When there are several examples, this will not work, as Rails.application.env_config(), which queries this setting, caches its result.
Mocking Rails.application.env_config() to return a modified result
context 'in production' do
before do
# We don't really want to test in a production environment,
# just in a slightly deviating test environment,
# so use the current test environment as a starting point ...
pseudo_production_config = Rails.application.env_config.clone
# ... and just remove the one test-specific setting we don't want here:
pseudo_production_config.delete 'action_dispatch.show_exceptions'
# Then let `Rails.application.env_config()` return that modified Hash
# for subsequent calls within this RSpec context.
allow(Rails.application).to receive(:env_config).
and_return pseudo_production_config
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
will do the trick. Note that we clone the result from env_config(), lest we modify the original Hash which would affect all examples.
context 'in production' do
around do |example|
# Run examples without the setting:
show_exceptions = Rails.application.env_config.delete 'action_dispatch.show_exceptions'
example.run
# Restore the setting:
Rails.application.env_config['action_dispatch.show_exceptions'] = show_exceptions
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
will do the trick, but feels kinda dirty. It works because Rails.application.env_config() gives access to the underlying Hash it uses for caching its result, so we can directly modify that.
In my opinion the exception test does not belong in a request spec; request specs are generally to test your api from the client's perspective to make sure your whole application stack is working as expected. They are also similar in nature to feature tests when testing a user interface. So because your clients won't be seeing this exception, it probably does not belong there.
I can also sympathize with your concern about using the framework correctly and wanting to make sure of that, but it does seem like you are getting too involved with the inner workings of the framework.
What I would do is first figure out whether I am using the feature in the framework correctly, (this can be done with a TDD approach or as a spike); once I understand how to accomplish what I want to accomplish, I'd write a request spec where I take the role of a client, and not worry about the framework details; just test the output given specific inputs.
I'd be interested to see the code that you have written in your controller because this can also be used to determine the testing strategy. If you wrote the code that raises the exception then that might justify a test for it, but ideally this would be a unit test for the controller. Which would be a controller test in an rspec-rails environment.

How to test ActionMailer deliver_later with rspec

trying to upgrade to Rails 4.2, using delayed_job_active_record. I've not set the delayed_job backend for test environment as thought that way jobs would execute straight away.
I'm trying to test the new 'deliver_later' method with RSpec, but I'm not sure how.
Old controller code:
ServiceMailer.delay.new_user(#user)
New controller code:
ServiceMailer.new_user(#user).deliver_later
I USED to test it like so:
expect(ServiceMailer).to receive(:new_user).with(#user).and_return(double("mailer", :deliver => true))
Now I get errors using that. (Double "mailer" received unexpected message :deliver_later with (no args))
Just
expect(ServiceMailer).to receive(:new_user)
fails too with 'undefined method `deliver_later' for nil:NilClass'
I've tried some examples that allow you to see if jobs are enqueued using test_helper in ActiveJob but I haven't managed to test that the correct job is queued.
expect(enqueued_jobs.size).to eq(1)
This passes if the test_helper is included, but it doesn't allow me to check it is the correct email that is being sent.
What I want to do is:
test that the correct email is queued (or executed straight away in test env)
with the correct parameters (#user)
Any ideas??
thanks
If I understand you correctly, you could do:
message_delivery = instance_double(ActionMailer::MessageDelivery)
expect(ServiceMailer).to receive(:new_user).with(#user).and_return(message_delivery)
allow(message_delivery).to receive(:deliver_later)
The key thing is that you need to somehow provide a double for deliver_later.
Using ActiveJob and rspec-rails 3.4+, you could use have_enqueued_job like this:
expect {
YourMailer.your_method.deliver_later
# or any other method that eventually would trigger mail enqueuing
}.to(
have_enqueued_job.on_queue('mailers').with(
# `with` isn't mandatory, but it will help if you want to make sure is
# the correct enqueued mail.
'YourMailer', 'your_method', 'deliver_now', any_param_you_want_to_check
)
)
also double check in config/environments/test.rb you have:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
Another option would be to run inline jobs:
config.active_job.queue_adapter = :inline
But keep in mind this would affect the overall performance of your test suite, as all your jobs will run as soon as they're enqueued.
If you find this question but are using ActiveJob rather than simply DelayedJob on its own, and are using Rails 5, I recommend configuring ActionMailer in config/environments/test.rb:
config.active_job.queue_adapter = :inline
(this was the default behavior prior to Rails 5)
I will add my answer because none of the others was good enough for me:
1) There is no need to mock the Mailer: Rails basically does that already for you.
2) There is no need to really trigger the creation of the email: this will consume time and slow down your test!
That's why in environments/test.rb you should have the following options set:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
Again: don't deliver your emails using deliver_now but always use deliver_later. That prevents your users from waiting for the effective delivering of the email. If you don't have sidekiq, sucker_punch, or any other in production, simply use config.active_job.queue_adapter = :async. And either async or inline for development environment.
Given the following configuration for the testing environment, you emails will always be enqueued and never executed for delivery: this prevents your from mocking them and you can check that they are enqueued correctly.
In you tests, always split the test in two:
1) One unit test to check that the email is enqueued correctly and with the correct parameters
2) One unit test for the mail to check that the subject, sender, receiver and content are correct.
Given the following scenario:
class User
after_update :send_email
def send_email
ReportMailer.update_mail(id).deliver_later
end
end
Write a test to check the email is enqueued correctly:
include ActiveJob::TestHelper
expect { user.update(name: 'Hello') }.to have_enqueued_job(ActionMailer::DeliveryJob).with('ReportMailer', 'update_mail', 'deliver_now', user.id)
and write a separate test for your email
Rspec.describe ReportMailer do
describe '#update_email' do
subject(:mailer) { described_class.update_email(user.id) }
it { expect(mailer.subject).to eq 'whatever' }
...
end
end
You have tested exactly that your email has been enqueued and not a generic job.
Your test is fast
You needed no mocking
When you write a system test, feel free to decide if you want to really deliver emails there, since speed doesn't matter that much anymore. I personally like to configure the following:
RSpec.configure do |config|
config.around(:each, :mailer) do |example|
perform_enqueued_jobs do
example.run
end
end
end
and assign the :mailer attribute to the tests were I want to actually send emails.
For more about how to correctly configure your email in Rails read this article: https://medium.com/#coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd
Add this:
# spec/support/message_delivery.rb
class ActionMailer::MessageDelivery
def deliver_later
deliver_now
end
end
Reference: http://mrlab.sk/testing-email-delivery-with-deliver-later.html
A nicer solution (than monkeypatching deliver_later) is:
require 'spec_helper'
include ActiveJob::TestHelper
describe YourObject do
around { |example| perform_enqueued_jobs(&example) }
it "sends an email" do
expect { something_that.sends_an_email }.to change(ActionMailer::Base.deliveries, :length)
end
end
The around { |example| perform_enqueued_jobs(&example) } ensures that background tasks are run before checking the test values.
I came with the same doubt and resolved in a less verbose (single line) way inspired by this answer
expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(#user).with(no_args)
Note that the last with(no_args) is essential.
But, if you don't bother if deliver_later is being called, just do:
expect(ServiceMailer).to expect(:new_user).with(#user).and_call_original
A simple way is:
expect(ServiceMailer).to(
receive(:new_user).with(#user).and_call_original
)
# subject
This answer is for Rails Test, not for rspec...
If you are using delivery_later like this:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
…
def create
…
# Yes, Ruby 2.0+ keyword arguments are preferred
UserMailer.welcome_email(user: #user).deliver_later
end
end
You can check in your test if the email has been added to the queue:
# test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
…
test 'email is enqueued to be delivered later' do
assert_enqueued_jobs 1 do
post :create, {…}
end
end
end
If you do this though, you’ll surprised by the failing test that tells you assert_enqueued_jobs is not defined for us to use.
This is because our test inherits from ActionController::TestCase which, at the time of writing, does not include ActiveJob::TestHelper.
But we can quickly fix this:
# test/test_helper.rb
class ActionController::TestCase
include ActiveJob::TestHelper
…
end
Reference:
https://www.engineyard.com/blog/testing-async-emails-rails-42
For recent Googlers:
allow(YourMailer).to receive(:mailer_method).and_call_original
expect(YourMailer).to have_received(:mailer_method)
I think one of the better ways to test this is to check the status of job alongside the basic response json checks like:
expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.on_queue('mailers').with('mailer_name', 'mailer_method', 'delivery_now', { :params => {}, :args=>[] } )
I have come here looking for an answer for a complete testing, so, not just asking if there is one mail waiting to be sent, in addition, for its recipient, subject...etc
I have a solution, than comes from here, but with a little change:
As it says, the curial part is
mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }
The problem is that the parameters than mailer receives, in this case, is different from the parameters than receives in production, in production, if the first parameter is a Model, now in testing will receive a hash, so will crash
enqueued_jobs.first[:args]
["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]
So, if we call the mailer as UserMailer.welcome_email(#user).deliver_later the mailer receives in production a User, but in testing will receive {"_aj_globalid"=>"gid://forjartistica/User/1"}
All comments will be appreciate,
The less painful solution I have found is changing the way that I call the mailers, passing, the model's id and not the model:
UserMailer.welcome_email(#user.id).deliver_later
This answer is a little bit different, but may help in cases like a new change in the rails API, or a change in the way you want to deliver (like use deliver_now instead of deliver_later).
What I do most of the time is to pass a mailer as a dependency to the method that I am testing, but I don't pass an mailer from rails, I instead pass an object that will do the the things in the "way that I want"...
For example if I want to check that I am sending the right mail after the registration of a user... I could do...
class DummyMailer
def self.send_welcome_message(user)
end
end
it "sends a welcome email" do
allow(store).to receive(:create).and_return(user)
expect(mailer).to receive(:send_welcome_message).with(user)
register_user(params, store, mailer)
end
And then in the controller where I will be calling that method, I would write the "real" implementation of that mailer...
class RegistrationsController < ApplicationController
def create
Registrations.register_user(params[:user], User, Mailer)
# ...
end
class Mailer
def self.send_welcome_message(user)
ServiceMailer.new_user(user).deliver_later
end
end
end
In this way I feel that I am testing that I am sending the right message, to the right object, with the right data (arguments). And I am just in need of creating a very simple object that has no logic, just the responsibility of knowing how ActionMailer wants to be called.
I prefer to do this because I prefer to have more control over the dependencies I have. This is form me an example of the "Dependency inversion principle".
I am not sure if it is your taste, but is another way to solve the problem =).

How to use transactional fixtures in test unit

When i run test cases, i don't want to store records in my databases. How can i achieve it.
Here is my code :-
class Sample << Test::Unit::TestCase
def setup
# code
end
def teardown
# code
end
def test_sample
# code
end
end
Am using the following gem:-
gem 'test-unit'
to run tests and api call for GET/POST/PUT/DELETE methods to create/delete records in database.
I assume what you mean is that the objects you are using are not persisted in a local database, but rather in a remote system accessed via an API. That is, when you save an objects attributes, they are sent to a remote server via an API call.
Have a look at webmock. It works well with test/unit and test/minitest (which is test/unit on steroids). Basically you define the http call that should result from an action, and pass that to webmock. Then when the action is tested webmock will intercept the http call, and return a mocked response. If the action call is different to the one you defined, web mock will generate an error that will cause the test to fail.
So say on creating a sample, you expect a POST to example.com/samples with a sample attribute :foo set to 'bar', you could write a test like this:
def test_create_sample
data = 'bar'
sample = Sample.new
sample.foo = data
stub_request(:post, "example.com/samples/1").
with(:body => {:sample => {foo: data}})
assert sample.save
end
This also assumes that your save action checks the api response, and returns true if everything is OK.

Writing functional tests for facebooker controller?

Anyone have any tips for best practices for mocking out facebook requests in functional tests? Is it just as simple as adding all of the proper params to the request? Is there a way to stub those out?
I'm using facebooker, which comes with a mock service:
# A mock service that reads the Facebook response from fixtures
# Adapted from http://gist.github.com/44344
#
# Facebooker::MockService.fixture_path = 'path/to/dir'
# Facebooker::Session.current = Facebooker::MockSession.create
But when I write a basic get test, it tries to redirect the browser to the facebook page for adding the app, which I assume indicates that the mocking isn't working.
test "loads respondent" do
Facebooker::Session.current = Facebooker::MockSession.create
get :index
puts #response.body # => <html><body>You are being redirected.</body></html>
end
I got this working with the latest version of facebooker (1.0.58):
# test_helper.rb
require 'facebooker/mock/session'
require 'facebooker/mock/service'
Facebooker::MockService.fixture_path = File.join(RAILS_ROOT, 'test', 'fixtures', 'facebook')
Obviously you will have to create the facebook directory in fixtures, or put it wherever. Inside you have to add a folder for each facebook method, and an xml file for the different types of responses you want to test for. I had to add facebook.users.getInfo and facebook.users.hasAppPermission. The easiest is just to add a file named default.xml with the example code from the facebook wiki for those actions.
# Controller test
test "facebook action" do
get :index, {:fb_sig_added => true}, :facebook_session => Facebooker::MockSession.create
assert_response :success
end
The fb_sig_added param is necessary as far as I can tell, because the internal facebooker logic checks the params directly before checking the session on that one. Which seems a bit wanky to me but maybe there's a reason for that.

Resources