Skip to content

Commit

Permalink
feat: Allow integration with nodemailer-mock
Browse files Browse the repository at this point in the history
Create a MailerTransportFactory class and inject in MailerService
This change allow you to create a custom TransportFactory Class and
implement a custom transport link nodemailer-mock that creates a mock
to nodemailer SMTP calls.

Closes #341
  • Loading branch information
reinaldooli committed Aug 26, 2020
1 parent c416bc2 commit 9a6ecc5
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 19 deletions.
1 change: 0 additions & 1 deletion lib/constants/mailer-options.constant.ts

This file was deleted.

2 changes: 2 additions & 0 deletions lib/constants/mailer.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const MAILER_OPTIONS = 'MAILER_OPTIONS';
export const MAILER_TRANSPORT_FACTORY = 'MAILER_TRANSPORT_FACTORY';
7 changes: 7 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
/** Modules **/
export { MailerModule } from './mailer.module';

/** Constants **/
export {
MAILER_OPTIONS,
MAILER_TRANSPORT_FACTORY,
} from './constants/mailer.constant';

/** Interfaces **/
export { MailerOptions } from './interfaces/mailer-options.interface';
export { TemplateAdapter } from './interfaces/template-adapter.interface';
export { MailerOptionsFactory } from './interfaces/mailer-options-factory.interface';
export { ISendMailOptions } from './interfaces/send-mail-options.interface';
export { MailerTransportFactory } from './interfaces/mailer-transport-factory.interface';

/** Services **/
export { MailerService } from './mailer.service';
2 changes: 1 addition & 1 deletion lib/interfaces/mailer-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Options =
| SESTransport.Options
| TransportOptions;

type TransportType =
export type TransportType =
| Options
| SMTPTransport
| SMTPPool
Expand Down
6 changes: 6 additions & 0 deletions lib/interfaces/mailer-transport-factory.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as Mail from 'nodemailer/lib/mailer';
import { TransportType } from './mailer-options.interface';

export interface MailerTransportFactory {
createTransport(opts?: TransportType): Mail;
}
4 changes: 2 additions & 2 deletions lib/mailer-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ValueProvider } from '@nestjs/common/interfaces';
import { DynamicModule, Module, Global, Provider } from '@nestjs/common';

/** Constants **/
import { MAILER_OPTIONS } from './constants/mailer-options.constant';
import { MAILER_OPTIONS } from './constants/mailer.constant';

/** Interfaces **/
import { MailerOptions } from './interfaces/mailer-options.interface';
Expand Down Expand Up @@ -89,7 +89,7 @@ export class MailerCoreModule {
useFactory: async (optionsFactory: MailerOptionsFactory) => {
return optionsFactory.createMailerOptions();
},
inject: [options.useExisting! || options.useClass! ],
inject: [options.useExisting! || options.useClass!],
};
}
}
24 changes: 24 additions & 0 deletions lib/mailer-transport.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createTransport } from 'nodemailer';
import * as Mail from 'nodemailer/lib/mailer';

import {
MailerOptions,
TransportType,
} from './interfaces/mailer-options.interface';
import { MailerTransportFactory as IMailerTransportFactory } from './interfaces/mailer-transport-factory.interface';
import { Inject } from '@nestjs/common';
import { MAILER_OPTIONS } from './constants/mailer.constant';

export class MailerTransportFactory implements IMailerTransportFactory {
constructor(
@Inject(MAILER_OPTIONS)
private readonly options: MailerOptions,
) {}

public createTransport(opts?: TransportType): Mail {
return createTransport(
opts || this.options.transport,
this.options.defaults,
);
}
}
55 changes: 53 additions & 2 deletions lib/mailer.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Test, TestingModule } from '@nestjs/testing';
import MailMessage = require('nodemailer/lib/mailer/mail-message');
import SMTPTransport = require('nodemailer/lib/smtp-transport');
import { MAILER_OPTIONS } from './constants/mailer-options.constant';
import { MailerOptions } from './interfaces/mailer-options.interface';
import * as nodemailerMock from 'nodemailer-mock';

import {
MAILER_OPTIONS,
MAILER_TRANSPORT_FACTORY,
} from './constants/mailer.constant';
import {
MailerOptions,
TransportType,
} from './interfaces/mailer-options.interface';
import { MailerTransportFactory } from './interfaces/mailer-transport-factory.interface';
import { MailerService } from './mailer.service';
import { HandlebarsAdapter } from './adapters/handlebars.adapter';
import { PugAdapter } from './adapters/pug.adapter';
Expand Down Expand Up @@ -53,6 +62,35 @@ function spyOnSmtpSend(onMail: (mail: MailMessage) => void) {
});
}

async function getMailerServiceWithCustomTransport(
options: MailerOptions,
): Promise<MailerService> {
class TestTransportFactory implements MailerTransportFactory {
createTransport(options?: TransportType) {
return nodemailerMock.createTransport({ host: 'localhost', port: -100 });
}
}
const module: TestingModule = await Test.createTestingModule({
providers: [
{
name: MAILER_OPTIONS,
provide: MAILER_OPTIONS,
useValue: options,
},
{
name: MAILER_TRANSPORT_FACTORY,
provide: MAILER_TRANSPORT_FACTORY,
useClass: TestTransportFactory,
},
MailerService,
],
}).compile();
await module.init();

const service = module.get<MailerService>(MailerService);
return service;
}

describe('MailerService', () => {
it('should not be defined if a transport is not provided', async () => {
await expect(getMailerServiceForOptions({})).rejects.toMatchInlineSnapshot(
Expand Down Expand Up @@ -237,4 +275,17 @@ describe('MailerService', () => {
'<p>Ejs test template. by Nest-modules TM</p>',
);
});

it('should use custom transport to send mail', async () => {
const service = await getMailerServiceWithCustomTransport({
transport: 'smtps://user@domain.com:pass@smtp.domain.com',
});
await service.sendMail({
to: 'user2@example.test',
subject: 'Test',
html: 'This is test.',
});

expect(nodemailerMock.mock.getSentMail().length).toEqual(1);
});
});
25 changes: 16 additions & 9 deletions lib/mailer.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/** Dependencies **/
import { get, defaultsDeep } from 'lodash';
import { Injectable, Inject } from '@nestjs/common';
import { createTransport, SentMessageInfo, Transporter } from 'nodemailer';
import { Injectable, Inject, Optional } from '@nestjs/common';
import { SentMessageInfo, Transporter } from 'nodemailer';
import * as previewEmail from 'preview-email';

/** Constants **/
import { MAILER_OPTIONS } from './constants/mailer-options.constant';
import {
MAILER_OPTIONS,
MAILER_TRANSPORT_FACTORY,
} from './constants/mailer.constant';

/** Interfaces **/
import { MailerOptions } from './interfaces/mailer-options.interface';
import { TemplateAdapter } from './interfaces/template-adapter.interface';
import { ISendMailOptions } from './interfaces/send-mail-options.interface';
import { MailerTransportFactory as IMailerTransportFactory } from './interfaces/mailer-transport-factory.interface';
import { MailerTransportFactory } from './mailer-transport.factory';

@Injectable()
export class MailerService {
Expand Down Expand Up @@ -42,7 +47,13 @@ export class MailerService {

constructor(
@Inject(MAILER_OPTIONS) private readonly mailerOptions: MailerOptions,
@Optional()
@Inject(MAILER_TRANSPORT_FACTORY)
private readonly transportFactory: IMailerTransportFactory,
) {
if (!transportFactory) {
this.transportFactory = new MailerTransportFactory(mailerOptions);
}
if (
(!mailerOptions.transport ||
Object.keys(mailerOptions.transport).length <= 0) &&
Expand Down Expand Up @@ -76,9 +87,8 @@ export class MailerService {
Object.keys(mailerOptions.transports).forEach((name) => {
this.transporters.set(
name,
createTransport(
this.transportFactory.createTransport(
this.mailerOptions.transports![name],
this.mailerOptions.defaults,
),
);
this.initTemplateAdapter(templateAdapter, this.transporters.get(name)!);
Expand All @@ -87,10 +97,7 @@ export class MailerService {

/** Transporter setup **/
if (mailerOptions.transport) {
this.transporter = createTransport(
this.mailerOptions.transport,
this.mailerOptions.defaults,
);
this.transporter = this.transportFactory.createTransport();
this.initTemplateAdapter(templateAdapter, this.transporter);
}
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@
"@types/pug": "2.0.4",
"@typescript-eslint/eslint-plugin": "3.9.1",
"@typescript-eslint/parser": "3.9.1",
"handlebars": "4.7.6",
"husky": "4.2.5",
"lint-staged": "10.2.11",
"jest": "26.4.2",
"lint-staged": "10.2.11",
"nodemailer": "6.4.11",
"nodemailer-mock": "^1.5.3",
"prettier": "2.0.5",
"reflect-metadata": "0.1.13",
"rimraf": "3.0.2",
Expand Down
12 changes: 10 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2110,7 +2110,7 @@ debug@3.1.0, debug@=3.1.0:
dependencies:
ms "2.0.0"

debug@4, debug@^4.1.0, debug@^4.1.1:
debug@4, debug@4.1.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
Expand Down Expand Up @@ -4772,12 +4772,20 @@ node-notifier@^8.0.0:
uuid "^8.3.0"
which "^2.0.2"

nodemailer-mock@^1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/nodemailer-mock/-/nodemailer-mock-1.5.3.tgz#449d8b99d9a3d5f49448744685e4f96e2142deb1"
integrity sha512-Y0JYLFjx+HMNB1Y55uYj+6Vk7ZkJyMKH6GY85UgaSsaaWdH7BE7j0pH7Tp8H4qvQkWSycDh/76iWlHb4dEiWtg==
dependencies:
debug "4.1.1"
nodemailer "6.x"

nodemailer@6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.0.tgz#91482ebc09d39156d933eb9e6159642cd27bf02c"
integrity sha512-UBqPOfQGD1cM3HnjhuQe+0u3DWx47WWK7lBjG5UtPnGOysr7oDK5lNCzcjK6zzeBSdTk4m1tGx1xNbWFZQmMNA==

nodemailer@6.4.11:
nodemailer@6.4.11, nodemailer@6.x:
version "6.4.11"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.11.tgz#1f00b4ffd106403f17c03f3d43d5945b2677046c"
integrity sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==
Expand Down

0 comments on commit 9a6ecc5

Please sign in to comment.