Skip to content

Commit a9a8e1a

Browse files
committed
Genric message
1 parent b845f21 commit a9a8e1a

File tree

2 files changed

+179
-1
lines changed

2 files changed

+179
-1
lines changed

example-application/test/setup/test-file-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { startWebServer, stopWebServer } from '../../entry-points/api';
99
import { Roles, User } from '../../libraries/types';
1010
const jwt = require('jsonwebtoken');
1111

12-
export type TestStartOptions = {
12+
export type TestStartOptions = {
1313
startAPI: boolean;
1414
disableNetConnect: boolean;
1515
includeTokenInHttpClient: boolean;

mocking.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
## **Mocking & contracts**
2+
3+
<br/>
4+
5+
### ⚪️ 1. Identify good mocks from bad mocks
6+
7+
🏷&nbsp; **Tags:** `#basic, #strategic`
8+
9+
:white_check_mark: &nbsp; **Do:** Mocking is neccessary evil we live with, in some cases they are just evil that will... often spoken about as one piece, it's useful to identify 3 types of by their purpose:
10+
11+
isolation - prevent and check interactions with externous units like email send... This is done by stubbing a function that makes externous calls or by intercepting network requests. Isolation embodies two motivation: first avoid hitting external systems also for cost reasons - sending thousands emails a day will come with a bill. Second motivation is checking that our code is making the call in the right way. Calls to _external_ system is an effect, an outcome, of our code which should be tested for the same reason we test the code response. Inherently this mocks sit at the borders of the unit under test. This is not only legit but mandatory
12+
13+
simulation - triggering some specific scenario that demands more than just an input like forcing an error or moving the time forward; Practically this means stubbing some function and triggering it to throw. While coupling the test to some internal mechanism or function is undesirable, it worth the risk if this an important scenario that can happen in production
14+
15+
Implementation - This one is fundamentally different: It checks that that the code _internally_ worked as expected for example by checking that a in-scope function was invoked or a state is as expected. Practically this mocking style will spy-on or stub a function and assert that it was invoked as and when expected
16+
17+
The key difference is in both isolation and simulation what the test check is external effects, outcomes and not implemenation. Implementation mocks are the only to check HOW the unit work and not WHAT it produces. This is the one that you want to avoid at any cose, a pure evil: it will fail when refactoring work even when the code is correct (false-positive) and will not warn when it shoud (false-negative), example here
18+
19+
Note: It's not the mocking technique that tells a bad mock, both stubs and spies can be used for legit and mocking and also to assert on implemenation details
20+
21+
Diagram unit under test borders vs internals?, 2x2, mocks differs by their purpose
22+
23+
Grey black green,
24+
25+
<br/>
26+
27+
👀 &nbsp; **Alternatives:** one might spin the backend in Docker container or just a separate Node process. This configuration better resembles the production but it will lack critical testing features as mentioned above ❌; Some teams run integration tests against production-like cloud environment (see bullet 'Reuse tests against production-like environment), this is a valid technique for extra validation but will get too slow and limiting to rely on during development ❌;
28+
29+
<br/>
30+
31+
<details><summary>✏ <b>Code Examples</b></summary>
32+
33+
```js
34+
const apiUnderTest = require('../api/start.js');
35+
36+
beforeAll(async () => {
37+
//Start the backend in the same process
38+
```
39+
40+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
41+
42+
</details>
43+
44+
<br/><br/>
45+
46+
### ⚪️ 2. Avoid hidden surprisng mocks
47+
48+
🏷&nbsp; **Tags:** `#strategic`
49+
50+
:white_check_mark: &nbsp; **Do:** Mocks change the code and test behaviour, reader must be aware of they do. Put the mocks definition inside the test if they directly affect the test outcome, otherwise define in beforeEach
51+
52+
About big JSON, the dynamic factory
53+
54+
<br/>
55+
56+
👀 &nbsp; **Alternatives:** Jest mocks library; vitest something
57+
58+
<br/>
59+
60+
<details><summary>✏ <b>Code Examples</b></summary>
61+
62+
```js
63+
// dynamic factory
64+
```
65+
66+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
67+
68+
</details>
69+
70+
<br/><br/>
71+
72+
### ⚪️ 3. Prefer full-mocks over partial ones
73+
74+
🏷&nbsp; **Tags:** `#advanced`
75+
76+
:white_check_mark: &nbsp; **Do:** When mocking we replace functions, having an object or class, should you replace one or all? Replacing one dangerous, a partial object - some mocked, some real, surprising effects and stability at risk. Code may hit other function and trigger charges
77+
78+
Alertnativelly, we can truncate the mocked object, assign some safe default to all functions (like throw) and the desired behaviour to a single function
79+
80+
When doing isolation type of mock, full-mock is desirable. We ensure no interaction with the external.
81+
82+
When doing simulation this rule skipped, we are forced to change implementation of internal object - changing all functions to the right behaviour is hard. We better take the risk of changing the one we need
83+
84+
<br/>
85+
86+
👀 &nbsp; **Alternatives:** Mocking a specific function of a class that interacts and incur charges might miss some other calls and result with charges
87+
88+
<br/>
89+
90+
<details><summary>✏ <b>Code Examples</b></summary>
91+
92+
```js
93+
// Set all functions to throw, change one function only
94+
```
95+
96+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
97+
98+
</details>
99+
100+
<br/><br/>
101+
102+
### ⚪️ 4. Clean-up all mocks BEFORE every test
103+
104+
🏷&nbsp; **Tags:** `#advanced`
105+
106+
:white_check_mark: &nbsp; **Do:**
107+
108+
<br/>
109+
110+
👀 &nbsp; **Alternatives:** After each...
111+
112+
<br/>
113+
114+
<details><summary>✏ <b>Code Examples</b></summary>
115+
116+
```js
117+
118+
```
119+
120+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
121+
122+
</details>
123+
124+
<br/><br/>
125+
126+
### ⚪️ 5. Be mindful about the mocking mechanism
127+
128+
🏷&nbsp; **Tags:** `#advanced`
129+
130+
:white_check_mark: &nbsp; **Do:** There are signifcant differences between two main mocking techniques, each with its own consequences. Module based mocks like jest.mock works by changing the import/require behaviour while cache-based mocks just import an object and modify its behaviour relying on the fact that one single instance will exist
131+
132+
Module-based mocking comes with a price, it need to intercept the import so this must happen before the code and the test import the desired code. About bundling. It's path sensitive?
133+
134+
Cache-based mocking on the othr hand relies on another assumption - existence of object in-memory that both the test and sut act upon. What if the code under test destructures...
135+
136+
<br/>
137+
138+
👀 &nbsp; **Alternatives:** Mocking
139+
140+
<br/>
141+
142+
<details><summary>✏ <b>Code Examples</b></summary>
143+
144+
```js
145+
146+
```
147+
148+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
149+
150+
</details>
151+
152+
<br/><br/>
153+
154+
### ⚪️ 6. Type your mocks
155+
156+
🏷&nbsp; **Tags:** `#advanced`
157+
158+
:white_check_mark: &nbsp; **Do:** There are signifcant differences between two main mocking techniques, each with its own consequences. Module based mocks like jest.mock works by changing the import/require behaviour while cache-based mocks just import an object and modify its behaviour relying on the fact that one single instance will exist
159+
160+
Module-based mocking comes with a price, it need to intercept the import so this must happen before the code and the test import the desired code. About bundling. It's path sensitive?
161+
162+
Cache-based mocking on the othr hand relies on another assumption - existence of object in-memory that both the test and sut act upon. What if the code under test destructures...
163+
164+
<br/>
165+
166+
👀 &nbsp; **Alternatives:** Mocking
167+
168+
<br/>
169+
170+
<details><summary>✏ <b>Code Examples</b></summary>
171+
172+
```js
173+
174+
```
175+
176+
➡️ [Full code here](https://github.com/testjavascript/integration-tests-a-z/blob/4c76cb2e2202e6c1184d1659bf1a2843db3044e4/example-application/entry-points/api-under-test.js#L10-L34)
177+
178+
</details>

0 commit comments

Comments
 (0)