Thu, Oct 28, 21, how to conduct unit testing for individual behavior, jes jasmine vs karma
This is a draft, the content is not complete and of poor quality!

webdev-node unit test

Thi

Angular Unit Testing

Unit Testing with Jest

  • 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

Why unit tests?

  1. difficulty to test a code means bad design
  2. gives us confidence that we can refactor our application, or add new features to it, without breaking existing functionalities
  3. In Angular, the default testing framework is Jasmine + test runner Karma

Rules

👇🏻 Source

  1. More isolated testing. We want to test the services and components in question, not their dependencies.
  2. Strongly typed template checking. Ensure: child def changes ⇒ dependent unit tests break
  3. 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.
  4. 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:
      1. should not be dependant on external services — if it requires some data to work, it should be injected via @Input();
      2. should not produce any side effects — if it needs to emit something, it should be emitted with @Output() instead;
      3. 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.

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 Mocks

    So 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)

  1. declarations: [ChildComponent] ⇒ BUT it’s not isolated, it depends on ChildComponent!
    1. Cons: If in ChildComponent, we add some dependency ⇒ not working
  2. Use schemas: NO_ERRORS_SCHEMA to tell the compiler to ignore any elements or attributes it isn’t familiar with
    1. 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.
  3. 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
    1. 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
  4. 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:

  1. 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.
  2. (read later) https://stackoverflow.com/questions/40541123/how-to-unit-test-if-an-angular-2-component-contains-another-component
  • Some examples of checking parent.

    Angular-Unit-Testing

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.
})

👇🏻 Source + codes

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.

👉 Official doc aboyut spyOn

// 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

TitleTypeExcerpt
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에 대한 정리