20

I've got a mailer function I've built and trying to shore up the coverage. Trying to test parts of it have proven tricky, specifically this mailer.smtpTransport.sendMail

var nodemailer = require('nodemailer')

var mailer = {}

mailer.smtpTransport = nodemailer.createTransport('SMTP', {
    'service': 'Gmail',
    'auth': {
        'XOAuth2': {
            'user': 'test@test.com',
            'clientId': 'googleClientID',
            'clientSecret': 'superSekrit',
            'refreshToken': '1/refreshYoSelf'
        }
    }
})
var mailOptions = {
    from: 'Some Admin <test@tester.com>',
}

mailer.verify = function(email, hash) {
    var emailhtml = 'Welcome to TestCo. <a href="'+hash+'">Click this '+hash+'</a>'
    var emailtxt = 'Welcome to TestCo. This  is your hash: '+hash
    mailOptions.to = email
    mailOptions.subject = 'Welcome to TestCo!'
    mailOptions.html = emailhtml
    mailOptions.text = emailtxt
    mailer.smtpTransport.sendMail(mailOptions, function(error, response){
        if(error) {
            console.log(error)

        } else {
            console.log('Message sent: '+response.message)
        }
    })
}

I'm unsure of how to go about testing, specifically ensuring that my mailer.smtpTransport.sendMail function is passing the correct parameters without actually sending the email. I'm trying to use https://github.com/whatser/mock-nodemailer/tree/master, but I'm probably doing it wrong. Should I be mocking out the method?

var _ = require('lodash')
var should = require('should')
var nodemailer = require('nodemailer')
var mockMailer = require('./helpers/mock-nodemailer')
var transport = nodemailer.createTransport('SMTP', '')

var mailer = require('../../../server/lib/account/mailer')

describe('Mailer', function() {
    describe('.verify()', function() {
        it('sends a verify email with a hashto an address when invoked', function(done) {
            var email ={
                'to': 'dave@testco.com',
                'html': 'Welcome to Testco. <a href="bleh">Click this bleh</a>',
                'text': 'Welcome to Testco. This  is your hash: bleh',
                'subject': 'Welcome to Testco!'
            }

            mockMailer.expectEmail(function(sentEmail) {
            return _.isEqual(email, sentEmail)
            }, done)
            mailer.verify('dave@testco.com','bleh')
            transport.sendMail(email, function() {})
    })
})
uxtx
  • 329
  • 1
  • 2
  • 9

4 Answers4

19

You can use a 'Stub' transport layer on your test instead of SMTP.

var stubMailer = require("nodemailer").createTransport("Stub"),
    options = {
        from: "from@email.com",
        to: "to@email.com",
        text: "My Message!"
    };

   stubMailer.sendMail(options, function(err, response){
     var message = response.message;
   })

So, in that case, 'message' will be the email in text format. Something like this:

MIME-Version: 1.0
X-Mailer: Nodemailer (0.3.43; +http://www.nodemailer.com/)
Date: Fri, 25 Feb 2014 11:11:48 GMT
Message-Id: <123412341234.e23232@Nodemailer>
From: from@email.com
To: to@email.com
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

My Message!

For more examples, take a look at nodemailer test suite: https://github.com/andris9/Nodemailer/blob/master/test/nodemailer-test.js

Robbie
  • 18,750
  • 4
  • 41
  • 45
rizidoro
  • 13,073
  • 18
  • 59
  • 86
  • 8
    What is the point of this test? Stubbing it out like that simply tests the nodemailer library, which is already being tested in the nodemailer test suite. Why not set up a dummy SMTP server and actually test the intended functionality? – Michael Aug 30 '16 at 03:22
  • 1
    @Michael you could inject the stub mailer and test your method to check if it send the email with correct data. The idea is not to test nodemailer, but your own methods that uses nodemailer in some way. The example below just illustrates how Stub transport works. – rizidoro Jun 09 '20 at 12:35
4

You can directly mock the sendMail function but it's not obvious how to access it from the tests. A Mailer instance is returned when you create a transport so you need to directly import that class in to your test.

const Mailer = require('nodemailer/lib/mailer')

Then you can mock or stub the sendMail method on the prototype in the usual way. Using Jasmine, you can do it like this:

beforeEach(function () {
  spyOn(Mailer.prototype, 'sendMail').and.callFake(function (mailOptions, cb) {
    cb(null, true)
  })
})

The callFake ensures that the sendMail's callback is still executed encase you need to test what happens next. You can easily simulate an error by passing a first argument to cb: cb(new Error('Email failed'))

Now that the mock is set up, you can check everything is working as intended:

expect(Mailer.prototype.sendMail).toHaveBeenCalled()
Geraint Anderson
  • 3,234
  • 4
  • 28
  • 49
2

expectEmail simply hooks into the transport layer, and expects you to identify the email ( return true if this is the email you are expecting ) by looking at the sentEmail contents.

In this case, return sentEmail.to === 'dave@testco.com' should suffice.

Keep in mind however, this module was designed in an environment where tests are ran in a random order and concurrently. You should propably randomize your data heavily to prevent collisions and false positives. BTW we use something like: var to = Date.now().toString(36) + Faker.Internet.email();

2

This example works fine for me

======== myfile.js ========

// SOME CODE HERE

transporter.sendMail(mailOptions, (err, info) => {
  // PROCESS RESULT HERE
});

======== myfile.spec.js (unit test file) ========

const sinon = require('sinon');
const nodemailer = require('nodemailer');
const sandbox = sinon.sandbox.create();

describe('XXX', () => {
  afterEach(function() {
    sandbox.restore();
  });

  it('XXX', done => {
    const transport = {
      sendMail: (data, callback) => {
        const err = new Error('some error');
        callback(err, null);
      }
    };
    sandbox.stub(nodemailer, 'createTransport').returns(transport);

    // CALL FUNCTION TO TEST

    // EXPECT RESULT
  });
});
K Chen
  • 33
  • 4
Alongkorn
  • 3,968
  • 1
  • 24
  • 41