E2E - Mocking MongoDB
This lesson is going to show you how to mock your data-source - MongoDB - for the E2E tests purpose. Thanks to using mongo-unit you will be able to perform actions that changes data in your DB, during E2E testing.
Mocking MongoDB#
E2E tests that you have written so far have one serious issue. They use the production database instance. That's a terrible design. Tests should never touch the production environment. Building the production version of the application for testing is good, but this application has a hardcoded database URL, which is bad. In this lesson, you will fix this issue. Apart from that, you will introduce a test that verifies if the user can add a product to the list of favorites.
Start by installing mongo-unit
- a library that mocks MongoDB. Using a mock rather than a localhost MongoDB instance means you don't need to bother about database state - it's always the same. To install mongo-unit
, type the following command in the terminal:
npm i -D mongo-unit
Now you need to prepare data that you want mongo-unit
to return. Create a new file, e2e/testData.js, and add the following code:
const mongodb = require('mongodb');
module.exports = {
products: [
{
_id: mongodb.ObjectId('5ed3bbefaf1c4c0e81d9b400'),
name: 'product1',
price: 2.4,
description: 'Lorem ipsum.',
image: 'juice',
},
{
_id: mongodb.ObjectId('5ed3bbefaf1c4c0e81d9b401'),
name: 'product2',
price: 0.5,
description: 'Lorem ipsum.',
image: 'tomato',
},
],
users: [
{
_id: mongodb.ObjectId('5ed3bbefaf1c4c0e81d9b400'),
email: '[email protected]',
password:
'c70b5dd9ebfb6f51d09d4132b7170c9d20750a7852f00680f65658f0310e810056e6763c34c9a00b0e940076f54495c169fc2302cceb312039271c43469507dc',
favorite: [],
},
],
};
Configuring Protractor#
The next step is to modify the Protractor configuration that is defined in e2e/protractor.conf.js. First of all, you need to introduce mongo-unit
, child_process
, and path
:
const mongoUnit = require("mongo-unit");
const childProcess = require("child_process");
const path = require("path");
You also need to set up a variable to hold a reference to the process that you are going to launch later:
let backEndProcess;
And function, that you will use to delay the promise resolve:
function delay(timeout) {
return () => {
new Promise((resolve) => {
setTimeout(resolve, timeout * 1000);
});
};
}
Now modify the onPrepare()
method to instantiate mongo-unit
and launch a Node.js instance with the application that you're going to test. Add the following return statement to it:
onPrepare() {
require("ts-node").register({
project: require("path").join(__dirname, "./tsconfig.json"),
});
jasmine.getEnv().addReporter(
new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY,
},
})
);
return mongoUnit
.start({ dbName: "cluster0" })
.then((testMongoUrl) => {
process.env.MONGO_URL = testMongoUrl;
})
.then(() => {
const testData = require("./testData.js");
mongoUnit.initDb(process.env.MONGO_URL, testData);
})
.then(() => {
const distFolder = path.join(
process.cwd(),
"dist/my-universal-app/server"
);
backEndProcess = childProcess.fork(distFolder + "/main.js");
})
.then(delay(10));
},
The last step is to introduce the onComplete()
method to shut down the Node.js instance that you've launched in onPrepare()
:
onComplete() {
backEndProcess.kill("SIGHUP");
},
Your configuration file should now look like the following:
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const mongoUnit = require("mongo-unit");
const childProcess = require("child_process");
const path = require("path");
const { SpecReporter, StacktraceOption } = require("jasmine-spec-reporter");
function delay(timeout) {
return () => {
new Promise((resolve) => {
setTimeout(resolve, timeout * 1000);
});
};
}
let backEndProcess;
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: ["./src/**/*.e2e-spec.ts"],
capabilities: {
browserName: "chrome",
},
directConnect: true,
baseUrl: "http://localhost:4200/",
framework: "jasmine",
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () {},
},
onPrepare() {
require("ts-node").register({
project: require("path").join(__dirname, "./tsconfig.json"),
});
jasmine.getEnv().addReporter(
new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY,
},
})
);
return mongoUnit
.start({ dbName: "cluster0" })
.then((testMongoUrl) => {
process.env.MONGO_URL = testMongoUrl;
})
.then(() => {
const testData = require("./testData.js");
mongoUnit.initDb(process.env.MONGO_URL, testData);
})
.then(() => {
const distFolder = path.join(
process.cwd(),
"dist/my-universal-app/server"
);
backEndProcess = childProcess.fork(distFolder + "/main.js");
})
.then(delay(10));
},
onComplete() {
backEndProcess.kill("SIGHUP");
},
};
This page is a preview of The newline Guide to Angular Universal