- webdev-node unit test
- Thi
- Angular Unit Testing
- Refs
- Why unit tests?
- Rules
- CLI
- Understanding concepts
- Getting started
- Errors?
webdev-node unit test
~~~ bash $ npm test -- --coverage > meadowlark@1.0.0 test > jest "--coverage" PASS lib/__tests__/handlers.test.js √ home page renders (3 ms) √ about page renders with fortune (1 ms) √ 404 handler renders √ 500 handler renders -------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | fortune.js | 100 | 100 | 100 | 100 | handlers.js | 100 | 100 | 100 | 100 | -------------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total Snapshots: 0 total Time: 1.448 s Ran all test suites. ~~~
Thi
Angular Unit Testing
-
TOC
-
Float left TOC
// Fix the TOC on the left sidebar // F12 and then paste below to the console window > Enter var toc = document.getElementsByClassName('notion-table_of_contents-block')[0]; var tocBlock = toc.parentElement.parentElement.parentElement.parentElement.parentElement; tocBlock.style.position = 'fixed'; tocBlock.style.marginLeft = '-1080px'; tocBlock.style.width = '280px'; tocBlock.style.top = '70px'; tocBlock.style.overflow = 'auto'; tocBlock.style.maxHeight = '700px';
Refs
- Ref to Max’s course.
- Ref to Max’s codes
- Official doc.
- Testing Components in Angular 2 with Jasmine
- Unit Tests: https://github.com/angular/angular-cli/wiki/test
- E2E Tests: https://github.com/angular/angular-cli/wiki/e2e
- Jasmine — behavior-driven javascript
- JEST docs ⇒
beforeEach
,afterAll
,…
Why unit tests?
- difficulty to test a code means bad design
- gives us confidence that we can refactor our application, or add new features to it, without breaking existing functionalities
- In Angular, the default testing framework is Jasmine + test runner Karma
Rules
👇🏻 Source
- More isolated testing. We want to test the services and components in question, not their dependencies.
- Strongly typed template checking. Ensure: child def changes ⇒ dependent unit tests break
- Less coupling between components and services. Dependency Injection makes it easy to inject services, but if those services are being used all over the shop, refactoring becomes difficult.
- Prefer dumb components over smart components. 👉 **This article explains it beautifully.
- dumb component (impured): doesn’t depend on outside data and produce side effects, just take its inputs and produce intended outputs.
- smart component (pured): handle not only its inputs but also external data ⇒ produce (also) side effects.
// wrong (impure) class DateTimePickerComponent { timeZone: string = "Europe/Warsaw"; constructor(private account: AccountService) { if (this.account.currentUser) { this.timeZone = this.account.currentUser.timeZone; } } changeTimeZone(timeZone: string) { this.timeZone = timeZone; this.account.updateCurrentUser({ timeZone }); } } // good (pure) class DateTimePickerComponent { @Input() timeZone: string = "Europe/Warsaw"; @Output() timeZoneChange: EventEmitter<string> = new EventEmitter(); changeTimeZone(timeZone: string) { this.timeZoneChange.emit(timeZone); } }
- Rules:
- should not be dependant on external services — if it requires some data to work, it should be injected via
@Input()
; - should not produce any side effects — if it needs to emit something, it should be emitted with
@Output()
instead; - should not mutate its’ inputs — because if it does, it actually produces a side effect that causes a change in the parent component’s data.A child should never directly edit parent’s data. If it needs to inform the parent that something had been changed, he should emit it as an event, which the parent should pick up and then properly act on it.
- should not be dependant on external services — if it requires some data to work, it should be injected via
CLI
// cd to app folder
ng test // test whole app
// Test only one file (jest.config.j s)
testMatch: ['**/web-integration-form.component.spec.ts'],
Understanding concepts
describe
breaks your test suite into components ⇒ more here.- You can also nest describes to further subdivide the suite.
describe
is for grouping,it
is for testing.-
stub
files? ⇒ Unit Testing in Angular: Stubs vs Spies vs MocksSo a stub is a function that replaces a real implementation of an existing function. Stubbing is, generally, an operation local to a test. We use stubs if we want to:
- control individual method behavior for a specific test case,
- prevent a method from making side effects like communicating with the outside world using Angular’s
HttpClient
.
- Sociable tests vs Solitary tests
- Sociable tests: often the tested unit relies on other units to fulfill its behavior.
- Solitary tests: some unit testers prefer to isolate the tested unit.
Jest vs Jasmine vs Karma?
- Jest is an integrated testing solution written by Facebook and famous especially in the React world.
- Instant feedback because he will run only the test files related to the changes.
- They’re different testing frameworks.
- Testing frameworks: Jest, Jasmine, Mocha
- Karma (by AngularJS team) = test runner (whereas others - jest/jasmine/mocha are testing frameworks)
- Karma is a type of test runner which creates a fake server, and then spins up tests in various browsers using data derived from that fake server. Karma is only a test runner, and requires a testing framework such as Mocha to plug into it in order to actually run tests.
Returned outputs
// example: web-integration-form.component.spec.ts
describe('WebIntegrationFormComponent', () => {
beforeEach();
it('should read', () => {});
describe('#setStatus_1', () ={
it('case 1: ...', () => {});
it('case 2: ...', () => {});
});
describe('#setStatus_2', () ={
it('case 1: ...', () => {});
it('case 2: ...', () => {});
});
});
// Each `it` runs separately
// Returns
✓ should create (197ms)
#setStatus_1
✓ case 1:...
✓ case 2:...
#setStatus_2
✓ case 1:...
✓ case 2:...
Webpack or not webpack?
// If using webpack based
// Because when you add "./" in styleUrls or templateURL,
// webpack produce your app
beforeEach(() => { // before each tests below this function
TestBed.configureTestingModule({
// TestBed let us create our components to test
declarations: [UserComponent] // which we wanna test?
});
});
// If not using webpack based
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UserComponent]
}).compileComponents();
});
👉 Jasmine — behavior-driven javascript
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
Isolated tests
Có nghĩa là nó không hề liên quan gì đến các components khác nhưng khi run ng serve
thì vẫn chạy test của toàn bộ app!
If component depends on Angular 2 ⇒ using TestBed
(and other things), otherwise, we can use isolated tests like below.
// reverse.pipe.spec.ts
import { ReversePipe } from "./reverse.pipe";
describe('Pipe: ReversePipe', () => {
it('should reverse the inputs', () => {
let reversePipe = new ReversePipe();
expect(reversePipe.transform('hello')).toEqual('olleh');
});
});
// reverse.pipe.ts
// doesn't depend on Angular 2
import { Pipe } from "@angular/core";
@Pipe({
name: 'reverse'
})
export class ReversePipe {
transform(value: string) {
return value.split("").reverse().join("");
}
}
Getting started
Is app properly created?
👉🏻 Difference: fixture.debugElement.componentInstance
& fixture.componentInstance
// First and nothing
it('should create the app', () => {
let fixture = TestBed.createComponent(UserComponent); // create a store new component in "fixture"
let app = fixture.debugElement.componentInstance; // need "debugElement" if without browsers
expect(app).toBeTruthy();
// example of inspect a property "title" of component
// (what comes in the class)
expect(app.title)...
});
Test a component
👉 Angular - Basics of testing components
Child component with input
👉 Good to read: [Towards Better Testing In Angular. Part 1 — Mocking Child Components | by Abdul Wahab Rafehi | Medium |
💡 Recommended in the Angular Testing Guide, is to manually mock (or stub) components
If cannot find child component errors? (source)
declarations: [ChildComponent]
⇒ BUT it’s not isolated, it depends onChildComponent
!- Cons: If in
ChildComponent
, we add some dependency ⇒ not working
- Cons: If in
- Use
schemas: NO_ERRORS_SCHEMA
to tell the compiler to ignore any elements or attributes it isn’t familiar with- Cons: if inside parent, we mispelling
<child></chidl>
or any wong things with child components ⇒ it still works but not really!! ⇒ There will be something wrong until we actually run the app. Difficult to test@Input
and@Output
.
- Cons: if inside parent, we mispelling
- Mock / Stub child component ⇒ ensure to have the same selector. ⇒ có thể tạo 1 cái “mock” class của
ChildComponent
bằng 1 file.stub
- Cons: it’s verbose + shortcoming if there are many inputs and outputs. It requires us to remember to change the stub each time we change the real component
- Using
ng-mock
// Wanna test user-item.component.spec.ts
// In its parent
<app-user-item
*ngFor="let conv of convList; let i=index; trackBy: trackByConvId"
[conversation]="conv"
></app-user-item>
Refs:
- Test parent and child components when passing data with input binding ⇒ defind in parent a simple child component (like the real one) + mocking a hero input if you wanna test child component.
- (read later) https://stackoverflow.com/questions/40541123/how-to-unit-test-if-an-angular-2-component-contains-another-component
-
Some examples of checking parent.
nativeElement
contains?
// Compiled html contains?
it('should display the user name if user is logged in', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
app.isLoggedIn = true;
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('p').textContent).toContain(app.user.name);
// not contains?
expect(compiled.querySelector('p').textContent).not.toContain(app.user.name);
});
Test a Service
If a component depends on many services, you need to create mocks of these services in which the function/properties you need to use in your component.
👉 Official doc (search for “The following WelcomeComponent depends on the UserService to know the name of the user to greet.”)
👉🏻 Official doc: Angular - Testing services
// An example but not very good
it('should use the user name from the service', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let userService = fixture.debugElement.injector.get(UserService);
fixture.detectChanges(); // <=======
// We need this line because it doesn't have the same state as init when we inject the service
// Without this, it's undefined at the beginning
expect(userService.user.name).toEqual(app.user.name);
});
// From: https://www.digitalocean.com/community/tutorials/testing-angular-with-jasmine-and-karma-part-1
import { TestBed } from '@angular/core/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let usersService: UsersService; // Add this
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UsersService]
});
usersService = TestBed.get(UsersService); // Add this
});
it('should be created', () => { // Remove inject()
expect(usersService).toBeTruthy();
});
});
Reactive From tests
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
👇🏻 Source.
TestBed.configureTestingModule({
// imports: [FormsModule] // import the FormsModule if you want ngModel to be working inside the test
schemas: [NO_ERRORS_SCHEMA] // remove the FormsModule import and use that schema to only shallow test your component. Please refer to the official document for more information.
})
it('should create a FormGroup comprised of FormControls', () => {
component.ngOnInit();
expect(component.formGroup instanceof FormGroup).toBe(true);
});
<dynamic-form [questions]="myQuestions"></dynamic-form>
The input questions
(in child) takes value from myQuestions
(in parent) ⇒ Life cycle hooks: check data-bound input > ngOnInit > other components.
👉🏻 Form & submit testing example (the same source as above): returned payload, setValue and submit,… + codes
it('should create a FormControl for each question', () => {
component.questions = [
{
controlType: 'text',
id: 'first',
label: 'My First',
required: false
},
{
controlType: 'text',
id: 'second',
label: 'Second!',
required: true
}
];
component.ngOnInit();
expect(Object.keys(component.formGroup.controls)).toEqual([
'first', 'second'
]);
});
Async
(src) A spy allows us to “spy” on a function and track attributes about it such as whether or not it was called, how many times it was called, and with which arguments it was called.
// Async tasks
it('shouldn\'t fetch data successfully if not called asynchronously', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
expect(app.data).toBe(undefined);
});
it('should fetch data successfully if called asynchronously', async(() => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(app.data).toBe('Data');
});
}));
// Alternative with tick
// ie. from "async...whenStable()" => "fakeAsync...tick()"
it('should fetch data successfully if called asynchronously', fakeAsync(() => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
tick(); // resolve it immediately, don't wanna wait
expect(app.data).toBe('Data');
}));
// DataService
export class DataService {
getDetails() {
const resultPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data');
}, 1500);
});
return resultPromise;
}
}
// UserComponent
this.dataService.getDetails().then((data: string) => this.data = data);
Errors?
// NullInjectorError: No provider for HttpClient!
beforeEach(async(() => {
import { HttpClientTestingModule } from '@angular/common/http/testing';
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
}).compileComponents();
}));
// NullInjectorError: No provider for AngularFireDatabase!
The following wiki, pages and posts are tagged with
Title | Type | Excerpt |
---|---|---|
2021-10-01-wiki-magnific-popup.md | post | 지킬 블로그에 이미지 확대 기능 추가하기 |
Jekyll + liquid | post | Monday-jekyll-liquid, jekyll install on mac and ubuntu using docker |
css tips for web development | post | Tue, Oct 19, 21, tools, fonts cascading and selectors |
auto focus when page loads | post | Tue, Oct 19, 21, auto-focus media-query google-font |
keycode css trics for search focus | post | Wednesday-keycode, how to find keycode for the kb input keys |
using css and jquery to switch between themes | post | Wed, Oct 20, 21, toggle icon, flash loading fix, DOM loaded before show content |
tools for webdev | post | Sat, Oct 23, 21, tools for webdev frameworks, drawing, visualization text-editor |
Complete guide to django rest framework and vue js | post | Wed, Nov 10, 21, Build professional REST APIs and spa with django vue |
mvc-mvt definition | post | Fri, Dec 24, 21, difference between mvc and mvt |
로컬 구글 Apps Script 개발 | post | 튜토리얼 따라하기 |
개발자의 글쓰기 | post | 개발자 글쓰기의 3원칙 : 정확성, 간결성, 가독성, 변수 네이밍부터 릴리스노트... |
파이썬 코딩의 기술 2판 | post | 똑똑하게 코딩하는 법 |
자바스크립트는 왜 그 모양일까? | page | 더글라스 크락포드가 알려주는 |
javascript for impatient | page | Exploit the power of modern javascript and avoid the pitfalls |
Practical Vim | post | 손이 먼저 반응하는 |
리팩토링 2판 | page | 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기 |
한 권으로 끝내는 정규표현식 | page | 여덟 가지 프로그래밍 언어별 완벽 해설 |
자바스크립트 코딩의 기술 | page | 똑똑하게 코딩하는 법 |
typescript로 개발시 파일 순서 문제 | post | typescript hoisting filepushorder |
css-meaning.md | post | toc의 css를 조정하다가 우연히 깨닫게 된 CSS 어원의 의미 |
Djangovue a fastrack to success | page | summary. |
refactoring.md | post | toc의 css를 조정하다가 우연히 깨닫게 된 CSS 어원의 의미Javascript 에서 출력된 html string을 브라우저로 확인하기 |
wiki-favicon.md | post | favicon 추가 방법, 관련 링크 |
wiki-innerhtml.md | post | View 에서 가져온 data 를 표기하는 과정에서 문득 value, textContent, innerHTML, innerText의 차이 |
wiki-toc2side.md | post | CSS및 SCSS에 대한 정리 |