@kensio/part-factory-test-data 1.3.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "part-factory-test-data",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"description": "How to build test data with @kensio/part-factory: keeping in the factory everything a test does not care about, passing dependencies at call time so factories stay independent and shareable, and choosing between its static, dynamic, variant and mapped factories.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Kensio Software",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kensio/part-factory-test-data",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "How to build test data with @kensio/part-factory: keeping in the factory everything a test does not care about, passing dependencies at call time so factories stay independent and shareable, and choosing between its static, dynamic, variant and mapped factories.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -5,7 +5,7 @@ description: Build test data with @kensio/part-factory, keeping in the factory e
|
|
|
5
5
|
|
|
6
6
|
# Building test data with Part Factory
|
|
7
7
|
|
|
8
|
-
[Part Factory](https://partfactory.dev/) (`@kensio/part-factory`) builds typed objects for tests
|
|
8
|
+
[Part Factory](https://partfactory.dev/) (`@kensio/part-factory`) builds typed objects for tests. A
|
|
9
9
|
factory holds the defaults, and `make(overrides)` returns an object with the overrides applied down
|
|
10
10
|
through the nested structure.
|
|
11
11
|
|
|
@@ -17,10 +17,10 @@ test cheap enough that nobody reaches for a shared fixture in the first place.
|
|
|
17
17
|
|
|
18
18
|
## Say only what the test is about
|
|
19
19
|
|
|
20
|
-
A factory defines every value the test
|
|
21
|
-
|
|
20
|
+
A factory defines every value the test ignores. The test can state only the values it does. That is
|
|
21
|
+
the whole point of one.
|
|
22
22
|
|
|
23
|
-
Without it, a test opens with twenty lines of construction and it is
|
|
23
|
+
Without it, a test opens with twenty lines of construction and it is unclear which of them the
|
|
24
24
|
assertions actually depend on. With it, the lines that are there are the lines that matter:
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
@@ -37,26 +37,26 @@ it("charges VAT on the order total", () => {
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
The prices are written down because the assertion is arithmetic on them. The order id, the customer
|
|
40
|
-
id and the dates
|
|
41
|
-
simple: **if an assertion depends on a value, put it in the test
|
|
42
|
-
it.**
|
|
40
|
+
id and the dates stay with the factory, because the test would read the same whatever they were. The
|
|
41
|
+
rule is that simple: **if an assertion depends on a value, put it in the test. Otherwise let the
|
|
42
|
+
factory supply it.**
|
|
43
43
|
|
|
44
44
|
This also removes a pressure that quietly damages production types. When constructing an object by
|
|
45
|
-
hand is painful, the tempting fix is to mark its required fields optional so tests can skip them
|
|
46
|
-
weakening the type for every caller in order to serve the tests. A factory makes the setup cheap
|
|
47
|
-
|
|
45
|
+
hand is painful, the tempting fix is to mark its required fields optional so tests can skip them,
|
|
46
|
+
weakening the type for every caller in order to serve the tests. A factory makes the setup cheap.
|
|
47
|
+
The type can go on saying what is actually required.
|
|
48
48
|
|
|
49
49
|
## Overrides are a deep partial
|
|
50
50
|
|
|
51
51
|
Overrides merge into the defaults rather than replacing them, all the way down the nested structure.
|
|
52
|
-
That is what makes "state only what matters" possible
|
|
52
|
+
That is what makes "state only what matters" possible. A test can set one field three levels deep
|
|
53
53
|
and leave its siblings alone.
|
|
54
54
|
|
|
55
55
|
Two consequences worth knowing:
|
|
56
56
|
|
|
57
57
|
- Arrays override by index, so `{ lines: [{ price: 1000 }] }` replaces the first default line and
|
|
58
58
|
leaves any others in place.
|
|
59
|
-
- An empty array
|
|
59
|
+
- An empty array leaves the defaults in place. To assert on emptiness, build the case some other way
|
|
60
60
|
rather than expecting `{ lines: [] }` to do it.
|
|
61
61
|
|
|
62
62
|
## Which factory
|
|
@@ -64,8 +64,8 @@ Two consequences worth knowing:
|
|
|
64
64
|
- **`StaticFactory`** when the defaults are fixed values. The simplest thing that works, and the
|
|
65
65
|
right default choice.
|
|
66
66
|
- **`DynamicFactory`** when the defaults have to be generated fresh for each object. Pair it with
|
|
67
|
-
[`@faker-js/faker`](https://fakerjs.dev/). This is what gives tests their isolation
|
|
68
|
-
email or a UUID means two tests cannot collide, so
|
|
67
|
+
[`@faker-js/faker`](https://fakerjs.dev/). This is what gives tests their isolation. A random
|
|
68
|
+
email or a UUID means two tests cannot collide, so tearing down is unnecessary.
|
|
69
69
|
- **`VariantFactory`** for a named variation of a base factory, when the variation is a concept the
|
|
70
70
|
tests talk about. `closedOfferFactory` reads better in ten tests than
|
|
71
71
|
`offerFactory.make({ closesAt: aMinuteAgo })` written ten times.
|
|
@@ -95,8 +95,8 @@ in the test is shorter and says more.
|
|
|
95
95
|
|
|
96
96
|
## Reach for MappedFactory only when the map is a real transformation
|
|
97
97
|
|
|
98
|
-
`MappedFactory` earns its place when the thing you want to override
|
|
99
|
-
want back. Good cases:
|
|
98
|
+
`MappedFactory` earns its place when the thing you want to override has a different shape from the
|
|
99
|
+
thing you want back. Good cases:
|
|
100
100
|
|
|
101
101
|
- Parts to an encoded form body: `{ email, password }` mapped to a
|
|
102
102
|
`application/x-www-form-urlencoded` string.
|
|
@@ -106,11 +106,11 @@ want back. Good cases:
|
|
|
106
106
|
|
|
107
107
|
If the mapping function is copying fields across into an object of the same shape, you wanted a
|
|
108
108
|
`DynamicFactory`. An identity map adds a type parameter, a second function and a layer of
|
|
109
|
-
indirection, and buys
|
|
109
|
+
indirection, and buys little.
|
|
110
110
|
|
|
111
111
|
## Pass dependencies at call time
|
|
112
112
|
|
|
113
|
-
A factory that needs something from the outside world
|
|
113
|
+
A factory that needs something from the outside world (a store, a client, a configured host)
|
|
114
114
|
declares it as a third type parameter and receives it as the second argument to `make`:
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
@@ -128,12 +128,12 @@ const order = await storedOrderFactory.make({ total: 5000 }, { orders });
|
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
Dependencies are given at call time rather than held by the factory, and are used as they are given,
|
|
131
|
-
never fetched or awaited. That is what keeps a factory shareable
|
|
132
|
-
reaches for
|
|
131
|
+
never fetched or awaited. That is what keeps a factory shareable. It holds no state of its own,
|
|
132
|
+
reaches for no ambient state, and cannot depend on what another factory did first. A factory built
|
|
133
133
|
this way can be used in every test file in the codebase and still stand on its own.
|
|
134
134
|
|
|
135
|
-
Keep each dependency as narrow as the factory actually needs. An `OrderStore` is a dependency
|
|
136
|
-
whole application is
|
|
135
|
+
Keep each dependency as narrow as the factory actually needs. An `OrderStore` is a dependency. The
|
|
136
|
+
whole application never is. A wide dependency is how state starts leaking between tests that were
|
|
137
137
|
supposed to be independent.
|
|
138
138
|
|
|
139
139
|
## Do not wrap a factory in a function that applies overrides
|
|
@@ -145,7 +145,7 @@ export function makeCustomer(overrides: Partial<Customer> = {}): Customer {
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
`make(overrides)` already is that function, and it does the job better
|
|
148
|
+
`make(overrides)` already is that function, and it does the job better. Its overrides are partial
|
|
149
149
|
all the way down the nested structure, where the spread above replaces a nested object whole.
|
|
150
150
|
|
|
151
151
|
A wrapper that does anything more than pass overrides through is a signal to read rather than to
|
|
@@ -155,17 +155,17 @@ write. It usually means one of two things:
|
|
|
155
155
|
computing, or deriving one field from another. Move that into a `DynamicFactory` defaults
|
|
156
156
|
function, which receives the overrides, or into a `MappedFactory` map.
|
|
157
157
|
- **The output type is fighting you.** The wrapper is casting, widening or filling in a field the
|
|
158
|
-
type demands but the test
|
|
159
|
-
|
|
158
|
+
type demands but the test ignores. Fix the type, or use `MappedFactory` so the parts and the
|
|
159
|
+
output are allowed to differ.
|
|
160
160
|
|
|
161
|
-
The same goes for a wrapper that exists to pass a dependency
|
|
161
|
+
The same goes for a wrapper that exists to pass a dependency. Declare it on the factory instead.
|
|
162
162
|
|
|
163
163
|
## Factories belong beside the type they construct
|
|
164
164
|
|
|
165
165
|
A library that defines an event, a message or a payload shape should export a factory for it.
|
|
166
166
|
Otherwise every consumer hand-rolls the literal, and every copy drifts.
|
|
167
167
|
|
|
168
|
-
The worked example
|
|
168
|
+
The worked example is an AWS Lambda function URL invocation event, payload format 2.0. It is around
|
|
169
169
|
thirty lines, of which two matter to any given test.
|
|
170
170
|
|
|
171
171
|
```json
|
|
@@ -186,7 +186,7 @@ thirty lines, of which two matter to any given test.
|
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
Hand-writing that in three test files gives three copies that drift as the shape changes. It also
|
|
189
|
-
carries a real trap
|
|
189
|
+
carries a real trap. The path is in the event twice, as `rawPath` and as `requestContext.http.path`,
|
|
190
190
|
and the query string is in it twice, as `rawQueryString` and as `queryStringParameters`. A test that
|
|
191
191
|
sets one and not the other passes against a handler reading the field the test set, and fails in
|
|
192
192
|
production against the same handler reading the other one.
|