1 | # Testing utilities and assertions for CDK libraries
|
2 |
|
3 |
|
4 | ---
|
5 |
|
6 | ![Deprecated](https://img.shields.io/badge/deprecated-critical.svg?style=for-the-badge)
|
7 |
|
8 | > This API may emit warnings. Backward compatibility is not guaranteed.
|
9 |
|
10 | ## Replacement recommended
|
11 |
|
12 | This library has been deprecated. We recommend you use the
|
13 | [@aws-cdk/assertions](https://docs.aws.amazon.com/cdk/api/v1/docs/assertions-readme.html) module instead.
|
14 |
|
15 | ---
|
16 |
|
17 |
|
18 |
|
19 | This library contains helpers for writing unit tests and integration tests for CDK libraries
|
20 |
|
21 | ## Unit tests
|
22 |
|
23 | Write your unit tests like this:
|
24 |
|
25 | ```ts
|
26 | const stack = new Stack();
|
27 |
|
28 | new MyConstruct(stack, 'MyConstruct', {
|
29 | ...
|
30 | });
|
31 |
|
32 | expect(stack).to(someExpectation(...));
|
33 | ```
|
34 |
|
35 | Here are the expectations you can use:
|
36 |
|
37 | ## Verify (parts of) a template
|
38 |
|
39 | Check that the synthesized stack template looks like the given template, or is a superset of it. These functions match logical IDs and all properties of a resource.
|
40 |
|
41 | ```ts
|
42 | matchTemplate(template, matchStyle)
|
43 | exactlyMatchTemplate(template)
|
44 | beASupersetOfTemplate(template)
|
45 | ```
|
46 |
|
47 | Example:
|
48 |
|
49 | ```ts
|
50 | expect(stack).to(beASupersetOfTemplate({
|
51 | Resources: {
|
52 | HostedZone674DD2B7: {
|
53 | Type: "AWS::Route53::HostedZone",
|
54 | Properties: {
|
55 | Name: "test.private.",
|
56 | VPCs: [{
|
57 | VPCId: { Ref: 'VPC06C5F037' },
|
58 | VPCRegion: { Ref: 'AWS::Region' }
|
59 | }]
|
60 | }
|
61 | }
|
62 | }
|
63 | }));
|
64 | ```
|
65 |
|
66 |
|
67 | ## Check existence of a resource
|
68 |
|
69 | If you only care that a resource of a particular type exists (regardless of its logical identifier), and that *some* of its properties are set to specific values:
|
70 |
|
71 | ```ts
|
72 | haveResource(type, subsetOfProperties)
|
73 | haveResourceLike(type, subsetOfProperties)
|
74 | ```
|
75 |
|
76 | Example:
|
77 |
|
78 | ```ts
|
79 | expect(stack).to(haveResource('AWS::CertificateManager::Certificate', {
|
80 | DomainName: 'test.example.com',
|
81 | // Note: some properties omitted here
|
82 |
|
83 | ShouldNotExist: ABSENT
|
84 | }));
|
85 | ```
|
86 |
|
87 | The object you give to `haveResource`/`haveResourceLike` like can contain the
|
88 | following values:
|
89 |
|
90 | - **Literal values**: the given property in the resource must match the given value *exactly*.
|
91 | - `ABSENT`: a magic value to assert that a particular key in an object is *not* set (or set to `undefined`).
|
92 | - special matchers for inexact matching. You can use these to match values based on more lenient conditions
|
93 | than the default (such as an array containing at least one element, ignoring the rest, or an inexact string
|
94 | match).
|
95 |
|
96 | The following matchers exist:
|
97 |
|
98 | - `objectLike(O)` - the value has to be an object matching at least the keys in `O` (but may contain
|
99 | more). The nested values must match exactly.
|
100 | - `deepObjectLike(O)` - as `objectLike`, but nested objects are also treated as partial specifications.
|
101 | - `exactValue(X)` - must match exactly the given value. Use this to escape from `deepObjectLike`'s leniency
|
102 | back to exact value matching.
|
103 | - `arrayWith(E, [F, ...])` - value must be an array containing the given elements (or matchers) in any order.
|
104 | - `stringLike(S)` - value must be a string matching `S`. `S` may contain `*` as wildcard to match any number
|
105 | of characters. Multiline strings are supported.
|
106 | - `anything()` - matches any value.
|
107 | - `notMatching(M)` - any value that does NOT match the given matcher (or exact value) given.
|
108 | - `encodedJson(M)` - value must be a string which, when decoded as JSON, matches the given matcher or
|
109 | exact value.
|
110 |
|
111 | Slightly more complex example with array matchers:
|
112 |
|
113 | ```ts
|
114 | expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
|
115 | PolicyDocument: {
|
116 | Statement: arrayWith(objectLike({
|
117 | Action: ['s3:GetObject'],
|
118 | Resource: ['arn:my:arn'],
|
119 | }})
|
120 | }
|
121 | }));
|
122 | ```
|
123 |
|
124 | ## Capturing values from a match
|
125 |
|
126 | Special `Capture` matchers exist to capture values encountered during a match. These can be
|
127 | used for two typical purposes:
|
128 |
|
129 | - Apply additional assertions to the values found during a matching operation.
|
130 | - Use the value found during a matching operation in a new matching operation.
|
131 |
|
132 | `Capture` matchers take an inner matcher as an argument, and will only capture the value
|
133 | if the inner matcher succeeds in matching the given value.
|
134 |
|
135 | Here's an example which asserts that a policy for `RoleA` contains two statements
|
136 | with *different* ARNs (without caring what those ARNs might be), and that
|
137 | a policy for `RoleB` *also* has a statement for one of those ARNs (again, without
|
138 | caring what the ARN might be):
|
139 |
|
140 | ```ts
|
141 | const arn1 = Capture.aString();
|
142 | const arn2 = Capture.aString();
|
143 |
|
144 | expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
|
145 | Roles: ['RoleA'],
|
146 | PolicyDocument: {
|
147 | Statement: [
|
148 | objectLike({
|
149 | Resource: [arn1.capture()],
|
150 | }),
|
151 | objectLike({
|
152 | Resource: [arn2.capture()],
|
153 | }),
|
154 | ],
|
155 | },
|
156 | }));
|
157 |
|
158 | // Don't care about the values as long as they are not the same
|
159 | expect(arn1.capturedValue).not.toEqual(arn2.capturedValue);
|
160 |
|
161 | expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
|
162 | Roles: ['RoleB'],
|
163 | PolicyDocument: {
|
164 | Statement: [
|
165 | objectLike({
|
166 | // This ARN must be the same as ARN1 above.
|
167 | Resource: [arn1.capturedValue]
|
168 | }),
|
169 | ],
|
170 | },
|
171 | }));
|
172 | ```
|
173 |
|
174 | NOTE: `Capture` look somewhat like *bindings* in other pattern matching
|
175 | libraries you might be used to, but they are far simpler and very
|
176 | deterministic. In particular, they don't do unification: if the same Capture
|
177 | is either used multiple times in the same structure expression or matches
|
178 | multiple times, no restarting of the match is done to make them all match the
|
179 | same value: the last value encountered by the `Capture` (as determined by the
|
180 | behavior of the matchers around it) is stored into it and will be the one
|
181 | available after the match has completed.
|
182 |
|
183 | ## Check number of resources
|
184 |
|
185 | If you want to assert that `n` number of resources of a particular type exist, with or without specific properties:
|
186 |
|
187 | ```ts
|
188 | countResources(type, count)
|
189 | countResourcesLike(type, count, props)
|
190 | ```
|
191 |
|
192 | Example:
|
193 |
|
194 | ```ts
|
195 | expect(stack).to(countResources('AWS::ApiGateway::Method', 3));
|
196 | expect(stack).to(countResourcesLike('AWS::ApiGateway::Method', 1, {
|
197 | HttpMethod: 'GET',
|
198 | ResourceId: {
|
199 | "Ref": "MyResource01234"
|
200 | }
|
201 | }));
|
202 | ```
|
203 |
|
204 | ## Check existence of an output
|
205 |
|
206 | `haveOutput` assertion can be used to check that a stack contains specific output.
|
207 | Parameters to check against can be:
|
208 |
|
209 | - `outputName`
|
210 | - `outputValue`
|
211 | - `exportName`
|
212 |
|
213 | If `outputValue` is provided, at least one of `outputName`, `exportName` should be provided as well
|
214 |
|
215 | Example
|
216 |
|
217 | ```ts
|
218 | expect(synthStack).to(haveOutput({
|
219 | outputName: 'TestOutputName',
|
220 | exportName: 'TestOutputExportName',
|
221 | outputValue: {
|
222 | 'Fn::GetAtt': [
|
223 | 'TestResource',
|
224 | 'Arn'
|
225 | ]
|
226 | }
|
227 | }));
|
228 | ```
|