Skip to content

Commit 1f3536b

Browse files
authored
Merge pull request #226 from Wolox/docs/-/unit-test-with-jest
[Jest] Add base unit test documentation
2 parents 546996b + 841488d commit 1f3536b

File tree

15 files changed

+807
-0
lines changed

15 files changed

+807
-0
lines changed

testing/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Wolox Testing Guides
22

33
- [LOAD Testing](./load-testing/README.md)
4+
- [Unit test - Jest](./unit-testing-jest/docs/unit-test-jest.md)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
## Angular default test configuration
2+
When you create a new component, directive, service, etc. The default configuration of the tests file should be something like this.
3+
4+
```ts
5+
import { ComponentFixture, TestBed } from '@angular/core/testing';
6+
import { ExampleComponent } from './example.component';
7+
8+
describe('ExampleComponent', () => {
9+
let component: ExampleComponent;
10+
let fixture: ComponentFixture<ExampleComponent>;
11+
12+
beforeEach(async () => {
13+
await TestBed.configureTestingModule({
14+
declarations: [ ExampleComponent ]
15+
})
16+
.compileComponents();
17+
});
18+
19+
beforeEach(() => {
20+
fixture = TestBed.createComponent(ExampleComponent);
21+
component = fixture.componentInstance;
22+
fixture.detectChanges();
23+
});
24+
25+
it('should create', () => {
26+
expect(component).toBeTruthy();
27+
});
28+
});
29+
```
30+
31+
This is the default configuration of a test file. As you can see, it uses a lot of what we already got on the top theory. Now let's understand the TestBed.
32+
33+
### Understanding TestBed :test_tube:
34+
[TestBed](https://angular.io/api/core/testing/TestBed) is a utility of Angular. With this, we can configure all the environments of our tests.
35+
36+
Configure a testing module
37+
With this we can set all the dependencies for our tests, for example:
38+
```ts
39+
TestBed.configureTestingModule(
40+
{
41+
declarations: [ ExampleComponent, OtherComponents ... ],
42+
imports: [ PipesModule, RouterTestingModule, OtherModules... ],
43+
providers: [ ExampleService, DecimalPipe, OtherProviders... ],
44+
},
45+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
46+
);
47+
```
48+
As you can see, is equal to any angular module configuration
49+
50+
### Fixture and Component :statue_of_liberty:
51+
Usually, when we need to test the render of the application, we declare fixture to get access to the render HTML and component to get the control of our class.
52+
53+
### Init the fixture
54+
```ts
55+
fixture = TestBed.createComponent(ExampleComponent);
56+
```
57+
This creates a background render of our component and is stored on fixture.
58+
59+
### Init the component
60+
```ts
61+
component = fixture.componentInstance;
62+
```
63+
In component, we store an instance of the class. Now, we can access the properties and methods of our class.
64+
65+
### fixture.detectChanges()
66+
Works like the OnPush detection strategy. With this function, we can update the render of our test, in case we need to use it.
67+
Moreover, when working with tests on Angular you must tell the `TestBed` to perform data binding by calling `fixture.detectChanges()` in
68+
order to update the DOM with the expected information.
69+
70+
### But why?
71+
According to Angular docs:
72+
> [Delayed change detection is intentional and useful. It gives the tester an opportunity to inspect and change the state of the component before Angular initiates data binding and calls lifecycle hooks.](https://angular.io/guide/testing-components-scenarios#detectchanges)
152 KB
Loading
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# How to get elements from the DOM on testing
2+
In order to create the most complete tests in our applications, we should include not only the logic functions but the
3+
DOM rendering as well. Therefore, we must know how to get access to the DOM elements in our test cases.
4+
So, les's see how it works:
5+
6+
## Getting element
7+
Consider the next element
8+
<br>
9+
``test.html``
10+
```html
11+
<button class="btn_login" (click)="login()">Login</button>
12+
```
13+
14+
``test.spec.ts``
15+
```ts
16+
it('should call login function', () => {
17+
// as nativeElement
18+
const button = fixture.debugElement.nativeELement.querySelector('.btn_login');
19+
button.click();
20+
21+
...
22+
23+
// as debugElement
24+
const button = fixture.debugElement.query(By.css('.btn_login'));
25+
button.triggerEventHandler('click', {});
26+
});
27+
```
28+
29+
When using `triggerEventHandler` note that the second parameter is the actual event object that will pass to the handler.
30+
31+
You can see more about ```nativeElement vs debugElement``` on this [link](https://angular.io/guide/testing-components-basics), also note that in debugElement we use the css class from ```By``` to use this you need to import from ```import { By } from '@angular/platform-browser';``` an you can see more info about ```By``` on this [link](https://angular.io/api/platform-browser/By)
32+
33+
Consider the next elements
34+
``test.html``
35+
```html
36+
<app-header-process
37+
[tabs]="data.tabs"
38+
(goTo)="goToPage($event)">
39+
</app-header-process>
40+
41+
<h1 class="title">{{data.title}}</h1>
42+
43+
<ul>
44+
<li class="list-item-data" *ngFor="let items from data.list">
45+
<p class="item-title">{{item.title}}</p>
46+
</li>
47+
</ul>
48+
```
49+
To get any of this elements we can use:
50+
``test.spec.ts``
51+
```ts
52+
it('should render information', () => {
53+
// get component
54+
const header = fixture.debugElement.query(By.css('app-header-process'));
55+
header.triggerEventHandler('goTo', '/main');
56+
expect(goToPage).toHaveBeenCalledWith('/main');
57+
58+
// get Texts elements
59+
const title = fixture.debugElement.query(By.css('.title'));
60+
expect(title.nativeElement.textContent).toBe('any text');
61+
62+
// get Iterable Elements
63+
const liItems = fixture.debugElement.queryAll(By.css('.list-item-data'));
64+
expect(liItems.length).toBe(5);
65+
66+
});
67+
```
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
2+
## HttpClientTestingModule
3+
To test services with the [HttpClient](https://angular.io/api/common/http/HttpClient) dependency, we can use the utilities of Angular testing. We don't need to use all the object utilities of HttpClient, so we use [HttpClientTestingModule](https://angular.io/api/common/http/testing/HttpClientTestingModule#description) to test, this is a soft module designed just for testing. For example:
4+
```ts
5+
import { Injectable } from '@angular/core';
6+
import { HttpClient } from '@angular/common/http';
7+
import { Observable } from 'rxjs';
8+
import { User } from '../models/user';
9+
10+
@Injectable({ providedIn: 'root' })
11+
export class UserService {
12+
constructor(private http: HttpClient) {}
13+
14+
findUserById(userId: number): Observable<User> {
15+
return this.http.get<User>(`api/users/${userId}`);
16+
}
17+
18+
findAllUsers(): Observable<User[]> {
19+
return this.http.get('api/users') as Observable<User[]>;
20+
}
21+
22+
saveUser(useId: number, changes: Partial<User>): Observable<User> {
23+
return this.http.put<User>(`api/user/${useId}`, changes);
24+
}
25+
}
26+
```
27+
28+
```ts
29+
import { TestBed } from '@angular/core/testing';
30+
import {
31+
HttpClientTestingModule,
32+
HttpTestingController,
33+
} from '@angular/common/http/testing';
34+
import { of } from 'rxjs';
35+
import { USERS } from '../data/data';
36+
import { UserService } from './user.service';
37+
38+
describe('UserService Using HttpClientTestingModule', () => {
39+
let service: UserService;
40+
let controller: HttpTestingController;
41+
beforeEach(() => {
42+
TestBed.configureTestingModule({
43+
imports: [HttpClientTestingModule],
44+
providers: [],
45+
});
46+
47+
service = TestBed.inject(UserService);
48+
controller = TestBed.inject(HttpTestingController);
49+
});
50+
51+
it('create', () => {
52+
expect(service).toBeTruthy();
53+
});
54+
```
55+
In this section, we configure the testing module with the dependency HttpClientTestingModule.
56+
Moreover, we don't need a fixture on this configuration due to the fact tha this is just a service
57+
and not a layout.
58+
59+
So, first of all we need to init the following variables:
60+
61+
1. The service
62+
a. `let service: UserService;`
63+
b. `service = TestBed.inject(userService);`
64+
65+
When using the `TestBed.inject` we are getting the instance of the injected dependency. This **doesn't** mean that
66+
we are injecting this dependency twice.
67+
68+
2. The controller
69+
a. `let controller: HttpTestingController;`
70+
b. `controller = TestBed.inject(HttpTestingController);`
71+
72+
In fact, the controller variable is optional, however, this dependency will help us to create more complete
73+
tests cases.
74+
75+
76+
```ts
77+
it('return all users', (done) => {
78+
service.findAllUsers().subscribe((users) => {
79+
expect(users).toEqual(USERS);
80+
done();
81+
});
82+
const req = controller.expectOne('api/users');
83+
expect(req.request.method).toEqual('GET');
84+
req.flush(USERS);
85+
});
86+
```
87+
In this test, we subscribe to ```findAllUsers```. Then, inside we create the assertion to verify the response data. ```done()``` is used to indicate the successful finish of the subscription.
88+
89+
With the [controller](https://angular.io/api/common/http/testing/HttpTestingController) we can mock and flush the request.
90+
91+
92+
```ts
93+
it('Should return user with specific Id', (done) => {
94+
const user = USERS[0];
95+
service.findUserById(user.id).subscribe((selectedUser) => {
96+
expect(selectedUser).toEqual(user);
97+
done();
98+
}, done.fail);
99+
const req = controller.expectOne(`api/user/${user.id}`);
100+
expect(req.request.method).toEqual('GET');
101+
req.flush(user);
102+
});
103+
104+
it('should update user', (done) => {
105+
const user = USERS[0];
106+
user.info.name = 'Andres';
107+
service.saveUser(user.id, user).subscribe((updatedUser) => {
108+
expect(updatedUser).toEqual(user);
109+
done();
110+
}, done.fail);
111+
const req = controller.expectOne(`api/user/${user.id}`);
112+
expect(req.request.method).toEqual('PUT');
113+
expect(req.request.body.info.name).toEqual(user.info.name);
114+
115+
req.flush(user);
116+
});
117+
118+
afterEach(() => {
119+
controller.verify();
120+
});
121+
});
122+
```
123+
Finally, in the afterEach we can verify that no unmatched requests are outstanding.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Whats is testing and why is important?
2+
Testing is a method to check if the code is working as expected. Testing the code helps to detect bugs on time and prevent the appearance of new bugs in the future. Also, testing gives confidence to our code. We can expect that they don't find problems in QA tests.
3+
4+
## Benefits
5+
* Fewer bugs in production
6+
* Fewer code regressions
7+
* More maintainable code
8+
* Faster refactoring and upgrades
9+
* Less dead code
10+
* You can see more on this [article](https://jestjs.io/docs/using-matchers).

0 commit comments

Comments
 (0)