Skip to content

Commit 18f9708

Browse files
author
Michael Solomon
authored
3rd party services isolation/8 (goldbergyoni#20)
1 parent 790b409 commit 18f9708

File tree

5 files changed

+128
-3
lines changed

5 files changed

+128
-3
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ dist
102102

103103
# TernJS port file
104104
.tern-port
105+
106+
# VSCode
107+
.vscode

example-application/api-under-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ const defineRoutes = (expressApp) => {
8181

8282
expressApp.use("/order", router);
8383

84-
expressApp.use((err, req, res, next) => {
84+
expressApp.use(async (err, req, res, next) => {
8585
console.log(err);
8686
if (process.env.SEND_MAILS === "true") {
8787
// important notification logic here
88-
mailer.send();
88+
await mailer.send('Error', err.message, 'admin@app.com');
8989

9090
// Other important notification logic here
9191
}
+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
module.exports.send = (subject, body, recipientAddress) => {
1+
const axios = require('axios');
2+
3+
module.exports.send = async (subject, body, recipientAddress) => {
24
console.log('Not really a mailer, right?', subject, body, recipientAddress);
5+
await axios.post(`https://mailer.com/send`, { subject, body, recipientAddress });
36
};

example-application/test/basic-tests.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ describe("/api", () => {
9898
id: 1,
9999
name: "John",
100100
});
101+
nock("http://localhost/").post(`/mailer`).reply(202);
101102
sinonSandbox.stub(OrderRepository.prototype, "addOrder").throws(new Error("Unknown error"));
102103
const spyOnMailer = sinon.spy(mailer, "send");
103104
const orderToAdd = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const request = require("supertest");
2+
const sinon = require("sinon");
3+
const nock = require("nock");
4+
const {
5+
initializeWebServer,
6+
stopWebServer
7+
} = require("../../../example-application/api-under-test");
8+
const OrderRepository = require("../../../example-application/data-access/order-repository");
9+
10+
let expressApp;
11+
let sinonSandbox;
12+
13+
beforeAll(async (done) => {
14+
// ️️️✅ Best Practice: Place the backend under test within the same process
15+
expressApp = await initializeWebServer();
16+
// ️️️✅ Best Practice: Ensure that this component is isolated by preventing unknown calls except for the Api-Under-Test
17+
nock.disableNetConnect();
18+
nock.enableNetConnect('127.0.0.1');
19+
20+
// ️️️✅ Best Practice: use a sandbox for test doubles for proper clean-up between tests
21+
sinonSandbox = sinon.createSandbox();
22+
23+
done();
24+
});
25+
26+
afterAll(async (done) => {
27+
// ️️️✅ Best Practice: Clean-up resources after each run
28+
await stopWebServer();
29+
done();
30+
});
31+
32+
beforeEach(() => {
33+
// ️️️✅ Best Practice: Isolate the service under test by intercepting requests to 3rd party services
34+
nock("http://localhost/user/").get(`/1`).reply(200, {
35+
id: 1,
36+
name: "John",
37+
})
38+
39+
if (sinonSandbox) {
40+
sinonSandbox.restore();
41+
}
42+
});
43+
44+
afterEach(() => {
45+
// ️️️✅ Best Practice: Clean nock interceptors between tests
46+
nock.cleanAll();
47+
})
48+
49+
// ️️️✅ Best Practice: Structure tests
50+
describe("/api", () => {
51+
describe("POST /orders", () => {
52+
test("When adding a new valid order , Then should get back 200 response", async () => {
53+
//Arrange
54+
const orderToAdd = {
55+
userId: 1,
56+
productId: 2,
57+
mode: "approved",
58+
};
59+
60+
//Act
61+
// ➿ Nock intercepts the request for users service as declared in the BeforeAll function
62+
const orderAddResult = await request(expressApp).post("/order").send(orderToAdd);
63+
64+
//Assert
65+
expect(orderAddResult.status).toBe(200);
66+
});
67+
68+
test("When the user does not exist, return http 404", async () => {
69+
//Arrange
70+
// ️️️✅ Best Practice: Simulate 3rd party service responses to test different scenarios like 404, 422 or 500.
71+
// Use specific params (like ids) to easily bypass the beforeEach interceptor.
72+
nock("http://localhost/user/").get(`/7`).reply(404, {
73+
message: "User does not exist",
74+
code: "nonExisting",
75+
});
76+
const orderToAdd = {
77+
userId: 7,
78+
productId: 2,
79+
mode: "draft",
80+
};
81+
82+
//Act
83+
const orderAddResult = await request(expressApp)
84+
.post("/order")
85+
.send(orderToAdd)
86+
87+
//Assert
88+
expect(orderAddResult.status).toBe(404);
89+
});
90+
91+
test("When order failed, send mail to admin", async () => {
92+
//Arrange
93+
process.env.SEND_MAILS = "true";
94+
sinonSandbox.stub(OrderRepository.prototype, "addOrder").throws(new Error("Unknown error"));
95+
// ️️️✅ Best Practice: Intercept requests for 3rd party services to eliminate undesired side effects like emails or SMS
96+
// ️️️✅ Best Practice: Specify the body when you need to make sure you call the 3rd party service as expected
97+
const scope = nock("https://mailer.com")
98+
.post('/send', {
99+
subject: /^(?!\s*$).+/,
100+
body: /^(?!\s*$).+/,
101+
recipientAddress: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
102+
})
103+
.reply(202);
104+
const orderToAdd = {
105+
userId: 1,
106+
productId: 2,
107+
mode: "approved",
108+
};
109+
110+
//Act
111+
await request(expressApp).post("/order").send(orderToAdd);
112+
113+
//Assert
114+
// ️️️✅ Best Practice: Assert that the app called the mailer service appropriately
115+
expect(scope.isDone()).toBe(true);
116+
});
117+
});
118+
});

0 commit comments

Comments
 (0)