| 
1 | 1 | ### Automock  | 
2 | 2 | 
 
  | 
3 |  | -Automock is a standalone library for unit testing. Using TypeScript Reflection  | 
4 |  | -API (`reflect-metadata`) internally to produce mock objects, Automock streamlines  | 
5 |  | -test development by automatically mocking class external dependencies.  | 
 | 3 | +Automock is a powerful standalone library designed for unit testing. It leverages the TypeScript Reflection API  | 
 | 4 | +internally to generate mock objects, simplifying the process of testing by automatically mocking external dependencies  | 
 | 5 | +of classes. Automock enables you to streamline test development and focus on writing robust and efficient unit tests.  | 
 | 6 | + | 
6 | 7 | > info **info** `Automock` is a third party package and is not managed by the NestJS core team.  | 
7 |  | -> Please, report any issues found with the library in the [appropriate repository](https://github.com/omermorad/automock)  | 
 | 8 | +> Please, report any issues found with the library in the [appropriate repository](https://github.com/automock/automock)  | 
8 | 9 | 
  | 
9 | 10 | #### Introduction  | 
10 | 11 | 
 
  | 
11 |  | -The dependency injection (DI) container is an essential component of the Nest module system.  | 
12 |  | -This container is utilized both during testing, and the application execution.  | 
13 |  | -Unit tests vary from other types of tests, such as integration tests, in that they must  | 
14 |  | -fully override providers/services within the DI container. External class dependencies  | 
15 |  | -(providers) of the so-called "unit", have to be totally isolated. That is, all dependencies  | 
16 |  | -within the DI container should be replaced by mock objects.  | 
17 |  | -As a result, loading the target module and replacing the providers inside it is a process  | 
18 |  | -that loops back on itself. Automock tackles this issue by automatically mocking all the  | 
19 |  | -class external providers, resulting in total isolation of the unit under test.  | 
 | 12 | +The Dependency Injection (DI) container is a foundational element of the Nest module system, integral to both application  | 
 | 13 | +runtime and testing phases. In unit tests, mock dependencies are essential for isolating and assessing the behavior of  | 
 | 14 | +specific components. However, the manual configuration and management of these mock objects can be intricate and prone to  | 
 | 15 | +errors.  | 
 | 16 | + | 
 | 17 | +Automock offers a streamlined solution. Rather than interacting with the actual Nest DI container, Automock introduces a  | 
 | 18 | +virtual container where dependencies are automatically mocked. This approach bypasses the manual task of substituting each  | 
 | 19 | +provider in the DI container with mock implementations. With Automock, the generation of mock objects for all dependencies  | 
 | 20 | +is automated, simplifying the unit test setup process.  | 
20 | 21 | 
 
  | 
21 | 22 | #### Installation  | 
22 | 23 | 
 
  | 
 | 24 | +Automock support both Jest and Sinon. Just install the appropriate package for your testing framework of choice.  | 
 | 25 | +Furthermore, you need to install the `@automock/adapters.nestjs` (as Automock supports other adapters).  | 
 | 26 | + | 
23 | 27 | ```bash  | 
24 |  | -$ npm i -D @automock/jest  | 
 | 28 | +$ npm i -D @automock/jest @automock/adapters.nestjs  | 
25 | 29 | ```  | 
26 | 30 | 
 
  | 
27 |  | -Automock does not require any additional setup.  | 
 | 31 | +Or, for Sinon:  | 
28 | 32 | 
 
  | 
29 |  | -> info **info** Jest is the only test framework currently supported by Automock.  | 
30 |  | -Sinon will shortly be released.  | 
 | 33 | +```bash  | 
 | 34 | +$ npm i -D @automock/sinon @automock/adapters.nestjs  | 
 | 35 | +```  | 
31 | 36 | 
 
  | 
32 | 37 | #### Example  | 
33 | 38 | 
 
  | 
34 |  | -Consider the following cats service, which takes three constructor parameters:  | 
 | 39 | +The example provided here showcase the integration of Automock with Jest. However, the same principles  | 
 | 40 | +and functionality applies for Sinon.  | 
 | 41 | + | 
 | 42 | +Consider the following `CatService` class that depends on a `Database` class to fetch cats. We'll mock  | 
 | 43 | +the `Database` class to test the `CatsService` class in isolation.  | 
35 | 44 | 
 
  | 
36 |  | -```ts  | 
37 |  | -@@filename(cats.service)  | 
38 |  | -import { Injectable } from '@nestjs/core';  | 
 | 45 | +```typescript  | 
 | 46 | +@Injectable()  | 
 | 47 | +export class Database {  | 
 | 48 | +  getCats(): Promise<Cat[]> { ... }  | 
 | 49 | +}  | 
39 | 50 | 
 
  | 
40 | 51 | @Injectable()  | 
41 |  | -export class CatsService {  | 
42 |  | -  constructor(  | 
43 |  | -    private logger: Logger,  | 
44 |  | -    private httpService: HttpService,  | 
45 |  | -    private catsDal: CatsDal,  | 
46 |  | -  ) {}  | 
47 |  | - | 
48 |  | -  async getAllCats() {  | 
49 |  | -    const cats = await this.httpService.get('http://localhost:3000/api/cats');  | 
50 |  | -    this.logger.log('Successfully fetched all cats');  | 
51 |  | -      | 
52 |  | -    this.catsDal.saveCats(cats);  | 
 | 52 | +class CatsService {  | 
 | 53 | +  constructor(private database: Database) {}  | 
 | 54 | + | 
 | 55 | +  async getAllCats(): Promise<Cat[]> {  | 
 | 56 | +    return this.database.getCats();  | 
53 | 57 |   }  | 
54 | 58 | }  | 
55 | 59 | ```  | 
56 | 60 | 
 
  | 
57 |  | -The service contains one public method, `getAllCats`, which is the method  | 
58 |  | -we use an example for the following unit test:  | 
 | 61 | +Let's set up a unit test for the `CatsService` class.  | 
 | 62 | + | 
 | 63 | +We'll use the `TestBed` from the `@automock/jest` package to create our test environment.  | 
59 | 64 | 
 
  | 
60 |  | -```ts  | 
61 |  | -@@filename(cats.service.spec)  | 
 | 65 | +```typescript  | 
62 | 66 | import { TestBed } from '@automock/jest';  | 
63 |  | -import { CatsService } from './cats.service';  | 
64 | 67 | 
 
  | 
65 |  | -describe('CatsService unit spec', () => {  | 
66 |  | -  let underTest: CatsService;  | 
67 |  | -  let logger: jest.Mocked<Logger>;  | 
68 |  | -  let httpService: jest.Mocked<HttpService>;  | 
69 |  | -  let catsDal: jest.Mocked<CatsDal>;  | 
70 |  | -    | 
 | 68 | +describe('Cats Service Unit Test', () => {  | 
 | 69 | +  let catsService: CatsService;  | 
 | 70 | +  let database: jest.Mocked<Database>;  | 
 | 71 | + | 
71 | 72 |   beforeAll(() => {  | 
72 |  | -    const { unit, unitRef } = TestBed.create(CatsService)  | 
73 |  | -      .mock(HttpService)  | 
74 |  | -      .using({ get: jest.fn() })  | 
75 |  | -      .mock(Logger)  | 
76 |  | -      .using({ log: jest.fn() })  | 
77 |  | -      .mock(CatsDal)  | 
78 |  | -      .using({ saveCats: jest.fn() })  | 
79 |  | -      .compile();  | 
80 |  | - | 
81 |  | -    underTest = unit;  | 
82 |  | - | 
83 |  | -    logger = unitRef.get(Logger);  | 
84 |  | -    httpService = unitRef.get(HttpService);  | 
85 |  | -    catsDal = unitRef.get(CatsDal);  | 
 | 73 | +    const { unit, unitRef } = TestBed.create(CatsService).compile();  | 
 | 74 | + | 
 | 75 | +    catsService = unit;  | 
 | 76 | +    database = unitRef.get(Database);  | 
86 | 77 |   });  | 
87 | 78 | 
 
  | 
88 |  | -  describe('when getting all the cats', () => {  | 
89 |  | -    test('then meet some expectations', async () => {  | 
90 |  | -      httpService.get.mockResolvedValueOnce([{ id: 1, name: 'Catty' }]);  | 
91 |  | -      await catsService.getAllCats();  | 
 | 79 | +  it('should retrieve cats from the database', async () => {  | 
 | 80 | +    const mockCats: Cat[] = [{ id: 1, name: 'Catty' }, { id: 2, name: 'Mitzy' }];  | 
 | 81 | +    database.getCats.mockResolvedValue(mockCats);  | 
 | 82 | + | 
 | 83 | +    const cats = await catsService.getAllCats();  | 
92 | 84 | 
 
  | 
93 |  | -      expect(logger.log).toBeCalled();  | 
94 |  | -      expect(catsDal).toBeCalledWith([{ id: 1, name: 'Catty' }]);  | 
95 |  | -    });  | 
 | 85 | +    expect(database.getCats).toHaveBeenCalled();  | 
 | 86 | +    expect(cats).toEqual(mockCats);  | 
96 | 87 |   });  | 
97 | 88 | });  | 
98 | 89 | ```  | 
99 | 90 | 
 
  | 
100 |  | -> info **info** The jest.Mocked<Source> utility type returns the Source type  | 
101 |  | -> wrapped with type definitions of Jest mock function. ([reference](https://jestjs.io/docs/mock-function-api/#jestmockedsource))  | 
 | 91 | +In the test setup, we:  | 
 | 92 | + | 
 | 93 | +1. Create a test environment for `CatsService` using `TestBed.create(CatsService).compile()`.  | 
 | 94 | +2. Obtain the actual instance of `CatsService` and a mocked instance of `Database` using `unit`  | 
 | 95 | +   and `unitRef.get(Database)`, respectively.  | 
 | 96 | +3. We mock the `getCats` method of the `Database` class to return a predefined list of cats.  | 
 | 97 | +4. We then call the `getAllCats` method of `CatsService` and verify that it correctly interacts with the `Database`  | 
 | 98 | +   class and returns the expected cats.  | 
102 | 99 | 
 
  | 
103 |  | -#### About `unit` and `unitRef`  | 
 | 100 | +**Adding a Logger**  | 
104 | 101 | 
 
  | 
105 |  | -Let's examine the following code:  | 
 | 102 | +Let's extend our example by adding a `Logger` interface and integrating it into the `CatsService` class.  | 
106 | 103 | 
 
  | 
107 | 104 | ```typescript  | 
108 |  | -const { unit, unitRef } = TestBed.create(CatsService).compile();  | 
 | 105 | +@Injectable()  | 
 | 106 | +class Logger {  | 
 | 107 | +  log(message: string): void { ... }  | 
 | 108 | +}  | 
 | 109 | + | 
 | 110 | +@Injectable()  | 
 | 111 | +class CatsService {  | 
 | 112 | +  constructor(private database: Database, private logger: Logger) {}  | 
 | 113 | + | 
 | 114 | +  async getAllCats(): Promise<Cat[]> {  | 
 | 115 | +    this.logger.log('Fetching all cats..');  | 
 | 116 | +    return this.database.getCats();  | 
 | 117 | +  }  | 
 | 118 | +}  | 
 | 119 | +```  | 
 | 120 | + | 
 | 121 | +Now, when you set up your test, you'll also need to mock the `Logger` dependency:  | 
 | 122 | + | 
 | 123 | +```typescript  | 
 | 124 | +beforeAll(() => {  | 
 | 125 | +  let logger: jest.Mocked<Logger>;  | 
 | 126 | +  const { unit, unitRef } = TestBed.create(CatsService).compile();  | 
 | 127 | + | 
 | 128 | +  catsService = unit;  | 
 | 129 | +  database = unitRef.get(Database);  | 
 | 130 | +  logger = unitRef.get(Logger);  | 
 | 131 | +});  | 
 | 132 | + | 
 | 133 | +it('should log a message and retrieve cats from the database', async () => {  | 
 | 134 | +  const mockCats: Cat[] = [{ id: 1, name: 'Catty' }, { id: 2, name: 'Mitzy' }];  | 
 | 135 | +  database.getCats.mockResolvedValue(mockCats);  | 
 | 136 | + | 
 | 137 | +  const cats = await catsService.getAllCats();  | 
 | 138 | + | 
 | 139 | +  expect(logger.log).toHaveBeenCalledWith('Fetching all cats..');  | 
 | 140 | +  expect(database.getCats).toHaveBeenCalled();  | 
 | 141 | +  expect(cats).toEqual(mockCats);  | 
 | 142 | +});  | 
109 | 143 | ```  | 
110 | 144 | 
 
  | 
111 |  | -Calling `.compile()` returns an object with two properties, `unit`, and `unitRef`.  | 
 | 145 | +**Using `.mock().using()` for Mock Implementation**  | 
112 | 146 | 
 
  | 
113 |  | -**`unit`** is the unit under test, it is an actual instance of class being tested.  | 
 | 147 | +Automock provides a more declarative way to specify mock implementations using the `.mock().using()` method chain.  | 
 | 148 | +This allows you to define the mock behavior directly when setting up the `TestBed`.  | 
114 | 149 | 
 
  | 
115 |  | -**`unitRef`** is the "unit reference", where the mocked dependencies of the tested class  | 
116 |  | -are stored, in a small container. The container's `.get()` method returns the mocked  | 
117 |  | -dependency with all of its methods automatically stubbed (using `jest.fn()`):  | 
 | 150 | +Here's how you can modify the test setup to use this approach:  | 
118 | 151 | 
 
  | 
119 | 152 | ```typescript  | 
120 |  | -const { unit, unitRef } = TestBed.create(CatsService).compile();  | 
 | 153 | +beforeAll(() => {  | 
 | 154 | +  const mockCats: Cat[] = [{ id: 1, name: 'Catty' }, { id: 2, name: 'Mitzy' }];  | 
 | 155 | + | 
 | 156 | +  const { unit, unitRef } = TestBed.create(CatsService)  | 
 | 157 | +    .mock(Database)  | 
 | 158 | +    .using({ getCats: async () => mockCats })  | 
 | 159 | +    .compile();  | 
121 | 160 | 
 
  | 
122 |  | -let httpServiceMock: jest.Mocked<HttpService> = unitRef.get(HttpService);  | 
 | 161 | +  catsService = unit;  | 
 | 162 | +  database = unitRef.get(Database);  | 
 | 163 | +});  | 
123 | 164 | ```  | 
124 | 165 | 
 
  | 
125 |  | -> info **info** The `.get()` method can accept either a `string` or an actual class (`Type`) as its argument.  | 
126 |  | -> This essentially depends on how the provider is being injected to the class under test.  | 
 | 166 | +In this approach, we've eliminated the need to manually mock the `getCats` method in the test body.  | 
 | 167 | +Instead, we've defined the mock behavior directly in the test setup using `.mock().using()`.  | 
 | 168 | + | 
 | 169 | +#### Dependency References and Instance Access  | 
 | 170 | + | 
 | 171 | +When utilizing `TestBed`, the `compile()` method returns an object with two important properties: `unit` and `unitRef`.  | 
 | 172 | +These properties provide access to the instance of the class under test and references to its dependencies, respectively.  | 
127 | 173 | 
 
  | 
128 |  | -#### Working with different providers  | 
129 |  | -Providers are one of the most important elements in Nest. You can think of many of  | 
130 |  | -the default Nest classes as providers, including services, repositories, factories,  | 
131 |  | -helpers, and so on. A provider's primary function is to take the form of an  | 
 | 174 | +`unit` - The unit property represents the actual instance of the class under test. In our example, it corresponds to an  | 
 | 175 | +instance of the `CatsService` class. This allows you to directly interact with the class and invoke its methods during  | 
 | 176 | +your test scenarios.  | 
 | 177 | + | 
 | 178 | +`unitRef` - The unitRef property serves as a reference to the dependencies of the class under test. In our example, it  | 
 | 179 | +refers to the `Logger` dependency used by the `CatsService`. By accessing `unitRef`, you can retrieve the automatically  | 
 | 180 | +generated mock object for the dependency. This enables you to stub methods, define behaviors, and assert method  | 
 | 181 | +invocations on the mock object.  | 
 | 182 | + | 
 | 183 | +#### Working with Different Providers  | 
 | 184 | + | 
 | 185 | +Providers are one of the most important elements in Nest. You can think of many of the default Nest classes as  | 
 | 186 | +providers, including services, repositories, factories, helpers, and so on. A provider's primary function is to take the  | 
 | 187 | +form of an  | 
132 | 188 | `Injectable` dependency.  | 
133 | 189 | 
 
  | 
134 |  | -Consider the following `CatsService`, it takes one parameter, which is an instance  | 
135 |  | -of the following `Logger` interface:  | 
 | 190 | +Consider the following `CatsService`, it takes one parameter, which is an instance of the following `Logger` interface:  | 
136 | 191 | 
 
  | 
137 | 192 | ```typescript  | 
138 | 193 | export interface Logger {  | 
139 | 194 |   log(message: string): void;  | 
140 | 195 | }  | 
141 | 196 | 
 
  | 
 | 197 | +@Injectable()  | 
142 | 198 | export class CatsService {  | 
143 | 199 |   constructor(private logger: Logger) {}  | 
144 | 200 | }  | 
145 | 201 | ```  | 
146 | 202 | 
 
  | 
147 |  | -TypeScript's Reflection API does not support interface reflection yet.  | 
148 |  | -Nest solves this issue with string-based injection tokens (see [Custom Providers](https://docs.nestjs.com/fundamentals/custom-providers)):  | 
 | 203 | +TypeScript's Reflection API does not support interface reflection yet. Nest solves this issue with string/symbol-based  | 
 | 204 | +injection tokens (see [Custom Providers](https://docs.nestjs.com/fundamentals/custom-providers)):  | 
149 | 205 | 
 
  | 
150 | 206 | ```typescript  | 
151 | 207 | export const MyLoggerProvider = {  | 
152 |  | -  provide: 'MY_LOGGER_TOKEN',  | 
 | 208 | +  provide: 'LOGGER_TOKEN',  | 
153 | 209 |   useValue: { ... },  | 
154 | 210 | }  | 
155 | 211 | 
 
  | 
 | 212 | +@Injectable()  | 
156 | 213 | export class CatsService {  | 
157 |  | -  constructor(@Inject('MY_LOGGER_TOKEN') private readonly logger: Logger) {}  | 
 | 214 | +  constructor(@Inject('LOGGER_TOKEN') readonly logger: Logger) {}  | 
158 | 215 | }  | 
159 | 216 | ```  | 
160 | 217 | 
 
  | 
161 |  | -Automock follows this practice and lets you provide a string-based token instead  | 
162 |  | -of providing the actual class in the `unitRef.get()` method:  | 
 | 218 | +Automock follows this practice and lets you provide a string-based (or symbol-based) token instead of providing the actual  | 
 | 219 | +class in the `unitRef.get()` method:  | 
163 | 220 | 
 
  | 
164 | 221 | ```typescript  | 
165 | 222 | const { unit, unitRef } = TestBed.create(CatsService).compile();  | 
166 | 223 | 
 
  | 
167 |  | -let loggerMock: jest.Mocked<Logger> = unitRef.get('MY_LOGGER_TOKEN');  | 
 | 224 | +let loggerMock: jest.Mocked<Logger> = unitRef.get('LOGGER_TOKEN');  | 
168 | 225 | ```  | 
169 | 226 | 
 
  | 
170 | 227 | #### More Information  | 
171 |  | -Visit [Automock GitHub repository](https://github.com/omermorad/automock) for more  | 
172 |  | -information.  | 
 | 228 | + | 
 | 229 | +Visit [Automock GitHub repository](https://github.com/automock/automock), or [Automock website](https://automock.dev) for more information.  | 
0 commit comments