ngx-speculoos 6.0.0 → 7.2.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.
- package/README.md +403 -42
- package/esm2020/jasmine-matchers.mjs +2 -0
- package/esm2020/lib/component-tester.mjs +147 -0
- package/esm2020/lib/matchers.mjs +283 -0
- package/esm2020/lib/mock.mjs +25 -0
- package/esm2020/lib/route.mjs +319 -0
- package/{esm2015/lib/test-button.js → esm2020/lib/test-button.mjs} +0 -0
- package/esm2020/lib/test-element-querier.mjs +140 -0
- package/esm2020/lib/test-element.mjs +142 -0
- package/{esm2015/lib/test-html-element.js → esm2020/lib/test-html-element.mjs} +0 -0
- package/{esm2015/lib/test-input.js → esm2020/lib/test-input.mjs} +0 -0
- package/{esm2015/lib/test-select.js → esm2020/lib/test-select.mjs} +0 -0
- package/{esm2015/lib/test-textarea.js → esm2020/lib/test-textarea.mjs} +0 -0
- package/{esm2015/ngx-speculoos.js → esm2020/ngx-speculoos.mjs} +0 -0
- package/{esm2015/public_api.js → esm2020/public_api.mjs} +2 -1
- package/fesm2015/ngx-speculoos.mjs +1257 -0
- package/fesm2015/ngx-speculoos.mjs.map +1 -0
- package/{fesm2015/ngx-speculoos.js → fesm2020/ngx-speculoos.mjs} +436 -22
- package/fesm2020/ngx-speculoos.mjs.map +1 -0
- package/jasmine-matchers.d.ts +5 -0
- package/lib/component-tester.d.ts +87 -49
- package/lib/mock.d.ts +10 -0
- package/lib/route.d.ts +149 -0
- package/lib/test-element-querier.d.ts +15 -15
- package/lib/test-element.d.ts +87 -49
- package/package.json +24 -11
- package/public_api.d.ts +1 -0
- package/bundles/ngx-speculoos.umd.js +0 -1259
- package/bundles/ngx-speculoos.umd.js.map +0 -1
- package/esm2015/jasmine-matchers.js +0 -2
- package/esm2015/lib/component-tester.js +0 -96
- package/esm2015/lib/matchers.js +0 -244
- package/esm2015/lib/route.js +0 -81
- package/esm2015/lib/test-element-querier.js +0 -129
- package/esm2015/lib/test-element.js +0 -91
- package/fesm2015/ngx-speculoos.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,13 +4,41 @@
|
|
|
4
4
|
# ngx-speculoos
|
|
5
5
|
|
|
6
6
|
ngx-speculoos helps you write simpler, cleaner unit tests for your Angular components, based on the
|
|
7
|
-
|
|
7
|
+
*page object* pattern. It also provides utilities to make writing Angular unit tests easier.
|
|
8
8
|
|
|
9
|
-
The library simply wraps the standard Angular ComponentFixture, and you should thus be
|
|
9
|
+
The library simply wraps the standard Angular ComponentFixture, and you should thus be
|
|
10
10
|
able to understand and start using ngx-speculoos in just a few minutes if you already know
|
|
11
11
|
how to write Angular unit tests.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Quick presentation](#quick-presentation)
|
|
16
|
+
- [Why should you care?](#why-should-you-care)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Getting started](#getting-started)
|
|
19
|
+
- [Features in details](#features-in-details)
|
|
20
|
+
- [ComponentTester](#componenttester)
|
|
21
|
+
- [Queries](#queries)
|
|
22
|
+
- [Queries for elements](#queries-for-elements)
|
|
23
|
+
- [CSS and Type selectors](#css-and-type-selectors)
|
|
24
|
+
- [Queries for sub components](#queries-for-sub-components)
|
|
25
|
+
- [Queries for injection tokens](#queries-for-injection-tokens)
|
|
26
|
+
- [Queries for custom TestElement](#queries-for-custom-testelement)
|
|
27
|
+
- [Subqueries](#subqueries)
|
|
28
|
+
- [Dispatching events](#dispatching-events)
|
|
29
|
+
- [Custom Jasmine matchers](#custom-jasmine-matchers)
|
|
30
|
+
- [Routing helper](#routing-helper)
|
|
31
|
+
- [Mocking helper](#mocking-helper)
|
|
32
|
+
- [Testing with a host component](#testing-with-a-host-component)
|
|
33
|
+
- [Gotchas](#gotchas)
|
|
34
|
+
- [When do I need to call detectChanges()](#when-do-i-need-to-call-detectchanges)
|
|
35
|
+
- [Can I use the TestElement methods to act on the component element itself, rather than a sub-element?](#can-i-use-the-testelement-methods-to-act-on-the-component-element-itself-rather-than-a-sub-element)
|
|
36
|
+
- [Issues, questions](#issues-questions)
|
|
37
|
+
- [Complete example](#complete-example)
|
|
38
|
+
|
|
39
|
+
## Quick presentation
|
|
40
|
+
|
|
41
|
+
### Why should you care?
|
|
14
42
|
|
|
15
43
|
If you've ever written tests like the following:
|
|
16
44
|
|
|
@@ -20,7 +48,7 @@ it('should display French cities when selecting the country France', () => {
|
|
|
20
48
|
countrySelect.selectedIndex = 12; // what is at index 12?
|
|
21
49
|
countrySelect.dispatchEvent(new Event('change')); // why do I need to do that?
|
|
22
50
|
fixture.detectChanges();
|
|
23
|
-
|
|
51
|
+
|
|
24
52
|
const city = fixture.nativeElement.querySelector('#city'); // city is of type any
|
|
25
53
|
expect(city).toBeTruthy();
|
|
26
54
|
expect(city.options.length).toBe(3);
|
|
@@ -37,29 +65,29 @@ it('should hide cities when selecting the empty country option', () => {
|
|
|
37
65
|
countrySelect.selectedIndex = 0;
|
|
38
66
|
countrySelect.dispatchEvent(new Event('change')); // why do I need to do that?
|
|
39
67
|
fixture.detectChanges(); // why do I need to do that?
|
|
40
|
-
|
|
68
|
+
|
|
41
69
|
expect(fixture.nativeElement.querySelector('#city')).toBeFalsy(); // I did that previously. What about DRY?
|
|
42
70
|
});
|
|
43
71
|
```
|
|
44
72
|
|
|
45
73
|
ngx-speculoos allows writing the above tests in a simpler, cleaner way:
|
|
46
74
|
|
|
47
|
-
- by using the page object pattern (which is optional, but recommended), you avoid repetitions.
|
|
48
|
-
- by using wrappers around elements, dispatching events and triggering change detection is automatic.
|
|
49
|
-
- by using wrappers around elements, you get useful additional methods to make tests easier to write and read.
|
|
50
|
-
- by using custom matchers, you get even simpler expectations and more readable error messages
|
|
51
|
-
- in any case you need them, you always have access to the fixture, the native elements, the debug elements, etc.
|
|
52
|
-
|
|
75
|
+
- by using the page object pattern (which is optional, but recommended), you avoid repetitions.
|
|
76
|
+
- by using wrappers around elements, dispatching events and triggering change detection is automatic.
|
|
77
|
+
- by using wrappers around elements, you get useful additional methods to make tests easier to write and read.
|
|
78
|
+
- by using custom matchers, you get even simpler expectations and more readable error messages
|
|
79
|
+
- in any case you need them, you always have access to the fixture, the native elements, the debug elements, etc.
|
|
80
|
+
|
|
53
81
|
```typescript
|
|
54
82
|
class MyComponentTester extends ComponentTester<MyComponent> {
|
|
55
83
|
constructor() {
|
|
56
84
|
super(MyComponent);
|
|
57
85
|
}
|
|
58
|
-
|
|
86
|
+
|
|
59
87
|
get country() {
|
|
60
88
|
return this.select('#country'); // returns a TestSelect object, not any. Similar methods exist for inputs, buttons, etc.
|
|
61
89
|
}
|
|
62
|
-
|
|
90
|
+
|
|
63
91
|
get city() {
|
|
64
92
|
return this.select('#city'); // returns a TestSelect object, not any
|
|
65
93
|
}
|
|
@@ -69,39 +97,241 @@ class MyComponentTester extends ComponentTester<MyComponent> {
|
|
|
69
97
|
|
|
70
98
|
it('should display French cities when selecting the country France', () => {
|
|
71
99
|
tester.country.selectLabel('France'); // no dispatchEvent, no detectChanges needed
|
|
72
|
-
|
|
100
|
+
|
|
73
101
|
expect(tester.city.optionValues).toEqual(['', 'PARIS', 'LYON']);
|
|
74
102
|
expect(tester.city.optionLabels).toEqual(['', 'Paris', 'Lyon']);
|
|
75
103
|
});
|
|
76
104
|
|
|
77
105
|
it('should hide cities when selecting empty country option', () => {
|
|
78
106
|
tester.country.selectIndex(0); // no repetition of the selector, no dispatchEvent, no detectChanges needed
|
|
79
|
-
|
|
107
|
+
|
|
80
108
|
expect(tester.city).toBeFalsy(); // no repetition of the selector
|
|
81
109
|
});
|
|
82
110
|
```
|
|
83
111
|
|
|
84
|
-
|
|
112
|
+
### Installation
|
|
113
|
+
|
|
114
|
+
Using the CLI: `ng add ngx-speculoos`
|
|
115
|
+
|
|
116
|
+
Using npm: `npm install --save-dev ngx-speculoos`
|
|
117
|
+
|
|
118
|
+
Using yarn: `yarn add --dev ngx-speculoos`
|
|
119
|
+
|
|
120
|
+
### Getting started
|
|
85
121
|
|
|
86
|
-
|
|
122
|
+
- import ComponentTester, and other needed classes from ngx-speculoos
|
|
123
|
+
- Create a `MyComponentTester` class (in your `my-component.spec.ts` file, typically) extending
|
|
124
|
+
`ComponentTester<MyComponent>`, as shown above.
|
|
125
|
+
- Expose getters (or methods, if you prefer) returning the elements used in your tests, using
|
|
126
|
+
one of the ComponentTester methods (`element`, `elements`, `input`, `select`, `textarea`, `button`, etc.).
|
|
127
|
+
See the [API documentation](https://ngx-speculoos.ninja-squad.com/documentation/classes/ComponentTester.html) for details
|
|
128
|
+
- Write your tests, as shown above, benefiting from the additional methods on the TestXxx classes.
|
|
129
|
+
- If needed, you can always get the fixture, componentInstance, debugElement, nativeElement, etc.
|
|
130
|
+
from the ComponentTester, and the nativeElement from each TestXxx wrapper.
|
|
131
|
+
- If you like our custom matchers, add them in a `beforeEach` block as shown above, and enjoy.
|
|
132
|
+
You can also add them for all tests at once by adding the beforeEach block to the CLI-generated `test.ts` file.
|
|
133
|
+
|
|
134
|
+
## Features in details
|
|
135
|
+
|
|
136
|
+
### ComponentTester
|
|
137
|
+
|
|
138
|
+
This is the entry point for most of the functionalities of ngx-speculoos. It wraps a `ComponentFixture`.
|
|
139
|
+
You can simply create one in your tests using
|
|
87
140
|
|
|
88
141
|
```typescript
|
|
89
|
-
|
|
142
|
+
const tester = new ComponentTester(MyComponent);
|
|
143
|
+
```
|
|
90
144
|
|
|
91
|
-
it
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
145
|
+
and then use it to query for sub elements, components, directives, etc. But we recommend adopting the
|
|
146
|
+
page object pattern, in order to make your test easier to write and read, and to avoid repeating the
|
|
147
|
+
same selectors over and over again.
|
|
148
|
+
|
|
149
|
+
You do that by writing a class that extends `ComponentTester`, and provides getters (or functions)
|
|
150
|
+
to query for elements, components, etc.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
class MyComponentTester extends ComponentTester<MyComponent> {
|
|
154
|
+
constructor() {
|
|
155
|
+
super(MyComponent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get country() {
|
|
159
|
+
return this.select('#country'); // returns a TestSelect object, not any. Similar methods exist for inputs, buttons, etc.
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get city() {
|
|
163
|
+
return this.select('#city'); // returns a TestSelect object, not any
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
and then in your tests, or in your `beforeEach`, once you've configured the testing module, you create
|
|
169
|
+
an instance of your component tester.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
describe('My component', () => {
|
|
173
|
+
let tester: MyComponentTester;
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
TestBed.configureTestingModule({
|
|
177
|
+
declarations: [MyComponent],
|
|
178
|
+
...
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
tester = new MyComponentTester();
|
|
182
|
+
tester.detectChanges();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it ('should ...`, () => {
|
|
186
|
+
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Queries
|
|
191
|
+
|
|
192
|
+
#### Queries for elements
|
|
193
|
+
|
|
194
|
+
Most of the queries that ngx-speculoos supports are used to query for DOM elements. The queries, however,
|
|
195
|
+
don't actually returns native DOM elements, but wrappers around them, which are instances of `TestElement`.
|
|
196
|
+
|
|
197
|
+
`TestElement` has more specialized subclasses: `TestHtmlElement`, `TestInput`, `TestSelect`, `TestTextarea`, `TestButton`.
|
|
198
|
+
Those subclasses offer helpful methods to get information or dispatch events to HTML elements, inputs, selects, etc.
|
|
199
|
+
Our custom matchers act on those `TestElement` objects.
|
|
200
|
+
|
|
201
|
+
You can [create your own subclasses of TestElement](#queries-for-custom-testelement) and query for them, too.
|
|
202
|
+
|
|
203
|
+
A TestElement is a wrapper around an Angular `DebugElement`. So it can access the `DebugElement` and the
|
|
204
|
+
native DOM element that it wraps. It also has an instance of the `ComponentTester` which created it,
|
|
205
|
+
which itself wraps the Angular `ComponentFixture` and thus allows detecting changes automatically after
|
|
206
|
+
an element has been dispatched, for example.
|
|
207
|
+
|
|
208
|
+
#### CSS and Type selectors
|
|
209
|
+
|
|
210
|
+
The first kind of query uses CSS selectors. This is simply a wrapper around Angular's `DebugElement.query(By.css())`.
|
|
211
|
+
The second kind of query uses directive types. This is simply a wrapper around Angular's `DebugElement.query(By.directive())`.
|
|
212
|
+
|
|
213
|
+
Whatever the kind of selector you choose, the methods are the same though:
|
|
214
|
+
|
|
215
|
+
- `element(selector)` to get the first element matching the selector
|
|
216
|
+
- `elements(selector)` to get an array of elements matching the selector
|
|
217
|
+
|
|
218
|
+
Both of those methods will automatically return a `TestInput`, or a `TestSelect`, or any other `TestElement`
|
|
219
|
+
subclass that ngx-speculoos provides based on the actual type of element being matched. But if you know
|
|
220
|
+
in advance what the result of the query is, you can use more-specific methods, or their generic parameter.
|
|
221
|
+
Passing an HTML element name as selector also automatically returns the right type
|
|
222
|
+
|
|
223
|
+
- `input(selector)` returns a `TestInput`
|
|
224
|
+
- `textarea(selector)` returns a `TestTextarea`
|
|
225
|
+
- `select(selector)` returns a `TestSelect`
|
|
226
|
+
- `button(selector)` returns a `TestButton`
|
|
227
|
+
- `element<HtmlInputElement>(selector)` returns a `TestInput`
|
|
228
|
+
- `element<HtmlDivElement>(selector)` returns a `TestHtmlElement<HtmlDivElement>`
|
|
229
|
+
- `elements<HtmlButtonElement>(selector)` returns an `Array<TestButton>`
|
|
230
|
+
- `element('input')` returns a `TestInput`
|
|
231
|
+
|
|
232
|
+
#### Queries for sub components
|
|
233
|
+
|
|
234
|
+
It's often useful to get the component instance of a sub component, for example to inspect its state,
|
|
235
|
+
or to make one of its outputs emit something. You can do that using the `component` and `components`
|
|
236
|
+
methods:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
get productIcon() {
|
|
240
|
+
return this.component(ProductIconComponent); // returns a ProductIconComponent
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
get reviewers() {
|
|
244
|
+
return this.components(ReviewerComponent); // returns an Array<ReviewerComponent>
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Queries for injection tokens
|
|
249
|
+
|
|
250
|
+
Querying using `element(DatepickerDirective)` will return you a `TestElement` on which the
|
|
251
|
+
`DatepickerDirective` has been applied.
|
|
252
|
+
|
|
253
|
+
If you need to get the `Datepicker` directive instance itself, then use the `token()` method
|
|
254
|
+
(or `tokens()` to get several of them)
|
|
255
|
+
which takes a selector (CSS or type) as first argument, and the token as second argument:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
get datepicker() {
|
|
259
|
+
return this.token('#birth-date', DatepickerDirective); // returns a DatepickerDirective instance
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Queries for custom `TestElement`
|
|
264
|
+
|
|
265
|
+
We provide `TestInput`, `TestSelect`, etc. to easily inspect or interact with inputs and selects in our tests.
|
|
266
|
+
But what if you want the same kind of test abstraction for your own reusable components or directives, like
|
|
267
|
+
for example your `DatepickerDirective`.
|
|
268
|
+
|
|
269
|
+
You can create your own `TestElement` subclass for that. This subclass must have a constructor that
|
|
270
|
+
takes a `ComponentTester` as first argument, and a `DebugElement` as second argument.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
class TestDatepicker extends TestHtmlElement<HTMLElement> {
|
|
274
|
+
constructor(tester: ComponentTester<unknown>, debugElement: DebugElement) {
|
|
275
|
+
super(tester, debugElement);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get inputField() {
|
|
279
|
+
return this.input('input');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setDate(year: number, month: number, day: number) {
|
|
283
|
+
this.inputField.fillWith(`${year}-${month}-${day}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
toggleDropdown() {
|
|
287
|
+
this.button('button').click();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Once you have created that class, you can use the `custom()` and `customs()` methods, using any selector,
|
|
293
|
+
to get instances of your custom `TestElement``
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
get birthDate() {
|
|
297
|
+
return this.custom('#birth-date', TestDatepicker);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
it('should not save if birth date is in the future') {
|
|
303
|
+
// ...
|
|
304
|
+
tester.birthDate.setDate(2200, 1, 1);
|
|
305
|
+
tester.save.click();
|
|
306
|
+
expect(userService.create).not.toHaveBenCalled();
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Subqueries
|
|
311
|
+
|
|
312
|
+
A query is made from the root `ComponentTester`. But `TestElement` themselves also support queries.
|
|
313
|
+
So you can query for a parent `TestElement`, and then use it to perform subqueries:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
get cardEditButton() {
|
|
317
|
+
return this.element('.card').button('.edit');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
get cardReviewerComponent() {
|
|
321
|
+
return this.element('.card').component(ReviewerComponent);
|
|
322
|
+
}
|
|
98
323
|
```
|
|
99
324
|
|
|
325
|
+
### Custom Jasmine matchers
|
|
326
|
+
|
|
327
|
+
We provide custom matchers, that act on `TestElement` and on its more specific subclasses (`TestInput`, `TestSelect`, etc.).
|
|
328
|
+
|
|
100
329
|
The complete matcher list includes:
|
|
101
330
|
|
|
102
331
|
- `toHaveClass(className: string)`
|
|
103
332
|
- `toHaveValue(value: string)`
|
|
104
333
|
- `toHaveText(textContent: string)`
|
|
334
|
+
- `toHaveTrimmedText(textContent: string)`
|
|
105
335
|
- `toContainText(textContent: string)`
|
|
106
336
|
- `toBeChecked()`
|
|
107
337
|
- `toHaveSelectedIndex(index: number)`
|
|
@@ -109,32 +339,163 @@ The complete matcher list includes:
|
|
|
109
339
|
- `toHaveSelectedLabel(label: string)`
|
|
110
340
|
- `toBeVisible()`
|
|
111
341
|
|
|
112
|
-
|
|
342
|
+
These matchers must be installed in each test using them:
|
|
113
343
|
|
|
114
|
-
|
|
344
|
+
```typescript
|
|
345
|
+
beforeEach(() => jasmine.addMatchers(speculoosMatchers));
|
|
346
|
+
```
|
|
115
347
|
|
|
116
|
-
|
|
348
|
+
or in all tests, by adding the above line of code in the `test.ts` file.
|
|
117
349
|
|
|
118
|
-
|
|
350
|
+
### Dispatching events
|
|
351
|
+
|
|
352
|
+
`TestElement` provides two methods that allow dispatching events in a simple way.
|
|
353
|
+
|
|
354
|
+
- `dispatchEvent(event: Event)`
|
|
355
|
+
- `dispatchEventOfType(type: string)`
|
|
356
|
+
|
|
357
|
+
Going through these methods automatically calls `detectChanges()` on the `ComponentTester` after the event has been dispatched,
|
|
358
|
+
so you don't need to call that yourself.
|
|
359
|
+
|
|
360
|
+
The TestElement subclasses that we provide have more specific event dispatching methods.
|
|
361
|
+
For example
|
|
362
|
+
|
|
363
|
+
- `TestHtmlElement.click()`
|
|
364
|
+
- `TestInput.fillWith()` for text, password, number, etc.
|
|
365
|
+
- `TestInput.check()` for radios and checkboxes
|
|
366
|
+
- `TestInput.uncheck()` for checkboxes
|
|
367
|
+
- `TestTextarea.fillWith()`
|
|
368
|
+
- `TestSelect.selectIndex()`
|
|
369
|
+
- `TestSelect.selectValue()`
|
|
370
|
+
- `TestSelect.selectLabel()`
|
|
371
|
+
|
|
372
|
+
Creating your own TestElement subclasses is a good way to provide such custom methods to interact
|
|
373
|
+
with your own reusable components in tests.
|
|
374
|
+
|
|
375
|
+
### Routing helper
|
|
376
|
+
|
|
377
|
+
The library provides a stub for the `ActivatedRoute` class that you typically inject in your routing components.
|
|
378
|
+
It mimics the behavior of the actual `ActivatedRoute`, by having a snapshot and observables that emit when this
|
|
379
|
+
snapshot changes. And it also allows simulating navigations by imperatively changing the parameters, query parameters,
|
|
380
|
+
etc.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { ActivatedRouteStub } from 'ngx-speculoos';
|
|
384
|
+
|
|
385
|
+
class RoutingComponentTester extends ComponentTester<RoutingComponent> {
|
|
386
|
+
// ...
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
describe('routing component', () => {
|
|
390
|
+
let route: ActivatedRouteStub;
|
|
391
|
+
let tester: RoutingComponentTester;
|
|
392
|
+
|
|
393
|
+
beforeEach(() => {
|
|
394
|
+
route = stubRoute({
|
|
395
|
+
params: { categoryId: 'pets' }
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
TestBed.configureTestingModule({
|
|
399
|
+
declarations: [RoutingComponent],
|
|
400
|
+
providers: [
|
|
401
|
+
{ provide: ActivatedRoute, useValue: route }
|
|
402
|
+
]
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
tester = new RoutingComponentTester();
|
|
406
|
+
tester.detectChanges();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should display all the products of the category', () => {
|
|
410
|
+
// test based on the initial route state
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should load other products when the category changes or when the query changes', () => {
|
|
414
|
+
route.setParam('category', 'toys');
|
|
415
|
+
tester.detectChanges();
|
|
416
|
+
// ...
|
|
417
|
+
|
|
418
|
+
route.setQueryParams({ 'max-price': '30', target: 'children' });
|
|
419
|
+
tester.detectChanges();
|
|
420
|
+
// ...
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Mocking helper
|
|
426
|
+
|
|
427
|
+
Jasmine is quite verbose when creating mock objects in a typesafe way:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const productService = jasmine.createSpyObj<ProductService>('ProductService', ['list', 'get', 'create', 'update']);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Since most of what we mock (usually Angular services) are classes, we can actually do a bit of introspection
|
|
434
|
+
and create a mock that will automatically mock all the methods declared in the class. That's what our
|
|
435
|
+
`createMock()` function does. The above code can thus be reduced to:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
const productService = createMock(ProductService);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Testing with a host component
|
|
442
|
+
|
|
443
|
+
ngx-speculoos doesn't provide any specific support for testing with host components, but we do it
|
|
444
|
+
a lot, simply by creating a ComponentTester for the host component rather than the component under test:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
@Component({
|
|
448
|
+
template: '<app-user [user]="user" (smile)="smiled = true"></app-user>'
|
|
449
|
+
})
|
|
450
|
+
class HostComponent {
|
|
451
|
+
user: User = {
|
|
452
|
+
id: 'u1',
|
|
453
|
+
name: 'John'
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
smiled = false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
class HostComponentTester extends ComponentTester<HostComponent> {
|
|
460
|
+
constructor() {
|
|
461
|
+
super(HostComponent);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
get userComponent() {
|
|
465
|
+
return this.component(UserComponent);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ...
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Once you have that, you can access the host component using `componentInstance()`,
|
|
473
|
+
the component under test using `userComponent()`, and any element of the component under test
|
|
474
|
+
using the usual queries.
|
|
475
|
+
|
|
476
|
+
## Gotchas
|
|
477
|
+
|
|
478
|
+
### When do I need to call `detectChanges()`?
|
|
479
|
+
|
|
480
|
+
Any event dispatched through a `TestElement` automatically calls `detectChanges()` for you.
|
|
481
|
+
But you still need to call `detectChanges()` by yourself in the other cases:
|
|
482
|
+
|
|
483
|
+
- to actually initialize your component. Sometimes, you want to configure some mocks before the `ngOnInit()`
|
|
484
|
+
method of your component is called. That's why creating a `ComponentTester` does not automatically call
|
|
485
|
+
`detectChanges()`. You need to do it yourself. The first call will cause the component lifecycle to start,
|
|
486
|
+
just as when using a `ComponentFixture` directly.
|
|
487
|
+
- to force change detection once you've changed the state of your component without dispatching an event:
|
|
488
|
+
by changing the state, or emitting an event through a subject, or triggering a navigation
|
|
489
|
+
from the `ActivatedRouteStub`
|
|
119
490
|
|
|
120
|
-
|
|
491
|
+
### Can I use the `TestElement` methods to act on the component element itself, rather than a sub-element?
|
|
121
492
|
|
|
122
|
-
|
|
123
|
-
- Create a `MyComponentTester` class (in your `my-component.spec.ts` file, typically) extending
|
|
124
|
-
`ComponentTester<MyComponent>`, as shown above.
|
|
125
|
-
- Expose getters (or methods, if you prefer) returning the elements used in your tests, using
|
|
126
|
-
one of the ComponentTester methods (`element`, `elements`, `input`, `select`, `textarea`, `button`).
|
|
127
|
-
See the API documentation for details
|
|
128
|
-
- Write your tests, as shown above, benefitting from the additional methods on the TestXxx classes.
|
|
129
|
-
- If needed, you can always get the fixture, componentInstance, debugElement, nativeElement, etc.
|
|
130
|
-
from the ComponentTester, and the nativeElement from each TestXxx wrapper.
|
|
131
|
-
- If you like our custom matchers, add them in a beforeEach block as shown above, and enjoy.
|
|
132
|
-
You can also add them for all tests at once by adding the beforeEach to block the CLI-generated `test.ts` file.
|
|
493
|
+
Yes. The `ComponentTester` has a `testElement` property, which is the `TestHtmlElement` wrapping the component's element.
|
|
133
494
|
|
|
134
495
|
## Issues, questions
|
|
135
496
|
|
|
136
497
|
Please, provide feedback by filing issues, or by submitting pull requests, to the [Github Project](https://github.com/Ninja-Squad/ngx-speculoos).
|
|
137
498
|
|
|
138
|
-
|
|
499
|
+
## Complete example
|
|
139
500
|
|
|
140
501
|
You can look at a minimal complete example in the [demo](https://github.com/Ninja-Squad/ngx-speculoos/tree/master/projects/demo/src/app) project.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiamFzbWluZS1tYXRjaGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL25neC1zcGVjdWxvb3Mvc3JjL2phc21pbmUtbWF0Y2hlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1uYW1lc3BhY2UsQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzICovXG5kZWNsYXJlIG5hbWVzcGFjZSBqYXNtaW5lIHtcbiAgaW50ZXJmYWNlIE1hdGNoZXJzPFQ+IHtcbiAgICAvKipcbiAgICAgKiBDaGVja3MgdGhhdCB0aGUgcmVjZWl2ZXIgaXMgYSBUZXN0RWxlbWVudCB3cmFwcGluZyBhIERPTSBlbGVtZW50IGFuZCBoYXMgdGhlIGdpdmVuIENTUyBjbGFzc1xuICAgICAqL1xuICAgIHRvSGF2ZUNsYXNzKGNsYXNzTmFtZTogc3RyaW5nKTogYm9vbGVhbjtcblxuICAgIC8qKlxuICAgICAqIENoZWNrcyB0aGF0IHRoZSByZWNlaXZlciBpcyBhIFRlc3RJbnB1dCBvciBhIFRlc3RUZXh0QXJlYSBhbmQgaGFzIHRoZSBnaXZlbiB2YWx1ZVxuICAgICAqL1xuICAgIHRvSGF2ZVZhbHVlKHZhbHVlOiBzdHJpbmcpOiBib29sZWFuO1xuXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIHRoYXQgdGhlIHJlY2VpdmVyIGlzIGEgVGVzdEVsZW1lbnQgd3JhcHBpbmcgYSBET00gZWxlbWVudCBhbmQgaGFzIHRoZSBleGFjdCBnaXZlbiB0ZXh0Q29udGVudFxuICAgICAqL1xuICAgIHRvSGF2ZVRleHQodGV4dENvbnRlbnQ6IHN0cmluZyk6IGJvb2xlYW47XG5cbiAgICAvKipcbiAgICAgKiBDaGVja3MgdGhhdCB0aGUgcmVjZWl2ZXIgaXMgYSBUZXN0RWxlbWVudCB3cmFwcGluZyBhIERPTSBlbGVtZW50IGFuZCBoYXMgdGhlIGdpdmVuIHRleHRDb250ZW50XG4gICAgICogYWZ0ZXIgYm90aCBoYXZlIGJlZW4gdHJpbW1lZC5cbiAgICAgKi9cbiAgICB0b0hhdmVUcmltbWVkVGV4dCh0ZXh0Q29udGVudDogc3RyaW5nKTogYm9vbGVhbjtcblxuICAgIC8qKlxuICAgICAqIENoZWNrcyB0aGF0IHRoZSByZWNlaXZlciBpcyBhIFRlc3RFbGVtZW50IHdyYXBwaW5nIGEgRE9NIGVsZW1lbnQgYW5kIGNvbnRhaW5zIHRoZSBnaXZlbiB0ZXh0Q29udGVudFxuICAgICAqL1xuICAgIHRvQ29udGFpblRleHQodGV4dENvbnRlbnQ6IHN0cmluZyk6IGJvb2xlYW47XG5cbiAgICAvKipcbiAgICAgKiBDaGVja3MgdGhhdCB0aGUgcmVjZWl2ZXIgaXMgYSBUZXN0SW5wdXQgYW5kIGlzIGNoZWNrZWRcbiAgICAgKi9cbiAgICB0b0JlQ2hlY2tlZCgpOiBib29sZWFuO1xuXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIHRoYXQgdGhlIHJlY2VpdmVyIGlzIGEgVGVzdFNlbGVjdCB3cmFwcGluZyBhIERPTSBlbGVtZW50IGFuZCBoYXMgdGhlIGdpdmVuIHNlbGVjdGVkIGluZGV4XG4gICAgICovXG4gICAgdG9IYXZlU2VsZWN0ZWRJbmRleChpbmRleDogbnVtYmVyKTogYm9vbGVhbjtcblxuICAgIC8qKlxuICAgICAqIENoZWNrcyB0aGF0IHRoZSByZWNlaXZlciBpcyBhIFRlc3RTZWxlY3Qgd3JhcHBpbmcgYSBET00gZWxlbWVudCB3aXRoIHRoZSBzZWxlY3RlZCBvcHRpb24ncyB2YWx1ZSBlcXVhbCB0byB0aGUgZ2l2ZW4gdmFsdWVcbiAgICAgKi9cbiAgICB0b0hhdmVTZWxlY3RlZFZhbHVlKHZhbHVlOiBzdHJpbmcpOiBib29sZWFuO1xuXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIHRoYXQgdGhlIHJlY2VpdmVyIGlzIGEgVGVzdFNlbGVjdCB3cmFwcGluZyBhIERPTSBlbGVtZW50IHdpdGggdGhlIHNlbGVjdGVkIG9wdGlvbidzIGxhYmVsIGVxdWFsIHRvIHRoZSBnaXZlbiBsYWJlbFxuICAgICAqL1xuICAgIHRvSGF2ZVNlbGVjdGVkTGFiZWwobGFiZWw6IHN0cmluZyk6IGJvb2xlYW47XG5cbiAgICAvKipcbiAgICAgKiBDaGVja3MgdGhhdCB0aGUgcmVjZWl2ZXIgaXMgYSBUZXN0SHRtbEVsZW1lbnQgd2hpY2ggaXMgdmlzaWJsZS5cbiAgICAgKi9cbiAgICB0b0JlVmlzaWJsZSgpOiBib29sZWFuO1xuICB9XG59XG4iXX0=
|