Skip to content

Commit a3c5163

Browse files
committed
06/01/solution add description
1 parent 0416155 commit a3c5163

File tree

1 file changed

+146
-0
lines changed
  • exercises/06.modules/01.solution.dependency-injection

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,149 @@
11
# Dependency injection
22

33
<EpicVideo url="https://www.epicweb.dev/workshops/mocking-techniques-in-vitest/modules/dependency-injection/solution" />
4+
5+
First, let's complete the `FakeFileStorage` class.
6+
7+
I will create an in-memory storage for the files on my `FakeFileStorage` class by defining a private property `data` and making it a `Map`:
8+
9+
```ts filename=upload-service.test.ts nonumber add=2
10+
class FakeFileStorage implements FileStorage {
11+
private data = new Map<string, Array<ArrayBuffer>>()
12+
}
13+
```
14+
15+
> I'm using `string` as the type argument for the keys stored in the map (i.e. file names), and `Array<ArrayBuffer>` as the value type representing a list of buffer chunks that belong to a single file.
16+
17+
Next, let's implement the `setItem()` method on the fake file storage class. Because I'm using `implements` for the fake class, TypeScript forces my fake class to be _type-compliant_ with the original file storage. But it doesn't have to be _implementation-compliant_.
18+
19+
In fact, I will implement the `setItem()` method by storing the given file in-memory, using the private `data` I've introduced earlier.
20+
21+
```ts filename=upload-service.test.ts nonumber add=4-7
22+
class FakeFileStorage implements FileStorage {
23+
private data = new Map<string, Array<ArrayBuffer>>()
24+
25+
public setItem(key: string, value: Array<ArrayBuffer>): Promise<void> {
26+
this.data.set(key, value)
27+
return Promise.resolve()
28+
}
29+
}
30+
```
31+
32+
> I'm using `Map.prototype.set` to store the `value` (the file chunks) by the file's `key`.
33+
34+
Next on the list is the `get()` method of the fake storage. For this one, since the original `getItem()` method returns a `Promise`, I will wrap my `data` value access in `Promise.resolve()`. This will make my fake method to return a promise that resolves to the result of looking up the file in the `data` map.
35+
36+
```ts filename=upload-service.test.ts nonumber add=9-11
37+
class FakeFileStorage implements FileStorage {
38+
private data = new Map<string, Array<ArrayBuffer>>()
39+
40+
public setItem(key: string, value: Array<ArrayBuffer>): Promise<void> {
41+
this.data.set(key, value)
42+
return Promise.resolve()
43+
}
44+
45+
public getItem(key: string): Promise<Array<ArrayBuffer> | undefined> {
46+
return Promise.resolve(this.data.get(key))
47+
}
48+
}
49+
```
50+
51+
With my file storage fake ready, I can continue with using it in tests.
52+
53+
In the first test, I will declare a `store` variable and assign it to be an instance of the `FakeFileStorage` class:
54+
55+
```ts filename=upload-service.test.ts nonumber add=2
56+
test('stores a small file in a single chunk', async () => {
57+
const storage = new FakeFileStorage()
58+
```
59+
60+
I can then provide the fake `storage` instance as an argument to the `UploadService` constructor to use the fake storage during the test run:
61+
62+
```ts filename=upload-service.test.ts nonumber add=4
63+
test('stores a small file in a single chunk', async () => {
64+
const storage = new FakeFileStorage()
65+
const uploadService = new UploadService({
66+
storage,
67+
maxChunkSize: 5,
68+
})
69+
```
70+
71+
The upload service instance for this test is configured to have `5` bytes as the maximum allowed chunk size:
72+
73+
```ts filename=upload-service.test.ts nonumber lines=2
74+
const uploadService = new UploadService({
75+
storage,
76+
maxChunkSize: 5,
77+
})
78+
```
79+
80+
Since I'm uploading a file with the content `'hello'`, and it's _exactly_ 5 bytes long, I am expecting a single chunk to be stored in my fake storage. Let's write an assertion just for that:
81+
82+
```ts filename=upload-service.test.ts nonumber add=14
83+
test('stores a small file in a single chunk', async () => {
84+
const storage = new FakeFileStorage()
85+
const uploadService = new UploadService({
86+
storage,
87+
maxChunkSize: 5,
88+
})
89+
90+
const storedItem = await uploadService.upload(
91+
new File(['hello'], 'hello.txt'),
92+
)
93+
94+
const chunks = storedItem.map((chunk) => Buffer.from(chunk).toString())
95+
96+
expect(chunks).toEqual(['hello'])
97+
})
98+
```
99+
100+
> To have a better diff when asserting on buffers (file content), I'm introducing a `chunks` variable that maps all the `storedItem` file chunks to _strings_ just for testing purposes.
101+
102+
But what about uploading larger files?
103+
104+
Jumping to the second test, I will create a fake storage instance as before, and provide it to the `UploadService` constructor.
105+
106+
```ts filename=upload-service.test.ts nonumber add=2,4
107+
test('splits a large file in multiple chunks', async () => {
108+
const storage = new FakeFileStorage()
109+
const uploadService = new UploadService({
110+
storage,
111+
maxChunkSize: 5,
112+
})
113+
```
114+
115+
Then, I will upload a larger file, containing `hello-world` as its content.
116+
117+
```ts filename=upload-service.test.ts nonumber
118+
const storedItem = await uploadService.upload(
119+
new File(['hello-world'], 'hello.txt'),
120+
)
121+
```
122+
123+
Since this file content exceeds the `maxChunkSize` of 5 bytes, I expect _three_ chunks to be uploaded to the fake storage:
124+
125+
- `hello` (first 5 bytes);
126+
- `-worl` (next 5 bytes);
127+
- `d` (the remaining 1 byte).
128+
129+
```ts filename=upload-service.test.ts nonumber add=14
130+
test('splits a large file in multiple chunks', async () => {
131+
const storage = new FakeFileStorage()
132+
const uploadService = new UploadService({
133+
storage,
134+
maxChunkSize: 5,
135+
})
136+
137+
const storedItem = await uploadService.upload(
138+
new File(['hello-world'], 'hello.txt'),
139+
)
140+
141+
const chunks = storedItem.map((chunk) => Buffer.from(chunk).toString())
142+
143+
expect(chunks).toEqual(['hello', '-worl', 'd'])
144+
})
145+
```
146+
147+
Now, these two file upload scenarios for my `UploadService` are passing in tests because I'm excluding the actual uploading functionality, delegating it to my `FakeFileStorage` class that _still stores the uploaded files_, doing that in memory.
148+
149+
This excludes the actual `FileStorage` implementation from the test since it's irrelevant, while simultaneously giving me access to the actually uploaded chunks to assert my upload service reliably.

0 commit comments

Comments
 (0)