Hey!
In my career, I haven't spent much time on front-end programming. However, I had it now!
It's a really exciting journey learning Angular/Karma/Jasmine and I feel like I will probably spent more time on it to gain more depth insights!
Today's article is my learning journey on this, hope you will find it as a great tutorial ^^
Introductions
Angular Testing Utilities
Angular is a TypeScript-based free and open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations. Angular is a complete rewrite from the same team that built AngularJS.
Angular testing utilities provide you a library to create a test environment for your application.
Classes such as TestBed and ComponentFixtures and helper functions such as async and fakeAsync are part of the @angular/core/testing package.
Getting acquainted with these utilities is necessary if you want to write tests that reveal how your components interact with their own template, services, and other components.
Ref Links
Karma
Karma is a tool that lets you test your application on multiple browsers.
Karma has plugins for browsers like Chrome, Firefox, Safari, and many others.
But I prefer using a headless browser for testing.
A headless browser lacks a GUI, and that way, you can keep the test results inside your terminal.
Ref Links
Jasmine
Jasmine is a popular behavior-driven testing framework for JavaScript. With Jasmine, you can write tests that are more expressive and straightforward.
Here is an example to get started:
it('should have a defined component', () => { expect(component).toBeDefined(); });
Ref Links
Steps
Environment
❯ nvm ls v12.13.1 -> v16.14.0 v17.6.0 default -> 16.14 (-> v16.14.0) iojs -> N/A (default) unstable -> N/A (default) node -> stable (-> v17.6.0) (default) stable -> 17.6 (-> v17.6.0) (default) lts/* -> lts/gallium (-> v16.14.0) lts/argon -> v4.9.1 (-> N/A) lts/boron -> v6.17.1 (-> N/A) lts/carbon -> v8.17.0 (-> N/A) lts/dubnium -> v10.24.1 (-> N/A) lts/erbium -> v12.22.10 (-> N/A) lts/fermium -> v14.19.0 (-> N/A) lts/gallium -> v16.14.0 ❯ npm -v 8.3.1 ❯ node -v v16.14.0 ❯ ng version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 13.2.6 Node: 16.14.0 Package Manager: npm 8.3.1 OS: darwin x64 Angular: 13.2.7 ... animations, common, compiler, compiler-cli, core, forms ... platform-browser, platform-browser-dynamic, router Package Version --------------------------------------------------------- @angular-devkit/architect 0.1302.6 @angular-devkit/build-angular 13.2.6 @angular-devkit/core 13.2.6 @angular-devkit/schematics 13.2.6 @angular/cli 13.2.6 @schematics/angular 13.2.6 rxjs 7.5.5 typescript 4.5.5
New An Angular Project
The developers at Angular have made it easy for us to set up our test environment. To get started, we need to install Angular first.
I prefer using the Angular-CLI. It's an all-in-one solution that takes care of creating, generating, building and testing your Angular project.
ng new Pastebin
Answer yes
to Would you like to add Angular routing?
; Answer CSS
to Which stylesheet format would you like to use? CSS
.
Directory structure:
❯ ls -l total 1568 -rw-r--r-- 1 geekcoding101 staff 1054 Apr 8 11:51 README.md -rw-r--r-- 1 geekcoding101 staff 3051 Apr 8 11:51 angular.json -rw-r--r-- 1 geekcoding101 staff 1425 Apr 8 11:51 karma.conf.js drwxr-xr-x 600 geekcoding101 staff 19200 Apr 8 11:53 node_modules -rw-r--r-- 1 geekcoding101 staff 773285 Apr 8 11:53 package-lock.json -rw-r--r-- 1 geekcoding101 staff 1071 Apr 8 11:51 package.json drwxr-xr-x 11 geekcoding101 staff 352 Apr 8 11:51 src -rw-r--r-- 1 geekcoding101 staff 287 Apr 8 11:51 tsconfig.app.json -rw-r--r-- 1 geekcoding101 staff 863 Apr 8 11:51 tsconfig.json -rw-r--r-- 1 geekcoding101 staff 333 Apr 8 11:51 tsconfig.spec.json ❯ tree src src ├── app │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── assets ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css └── test.ts 3 directories, 14 files
Launch Angular project:
Run karma:
You can define a headless browser in your karma.conf.js
as below:
... browsers: ['Chrome','ChromeNoSandboxHeadless'], customLaunchers: { ChromeNoSandboxHeadless: { base: 'Chrome', flags: [ '--no-sandbox', // See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md '--headless', '--disable-gpu', // Without a remote debugging port, Google Chrome exits immediately. ' --remote-debugging-port=9222', ], }, }, ...
You can refer to Cheatsheet about how to run unit test and specify which browser to run your test.
Add Class
ng generate class Pastebin
Pastebin.ts
:
export class Pastebin { id: number; title: string; language: string; paste: string; constructor(values: Object = {}) { Object.assign(this, values); } } export const Languages = ["Ruby","Java", "JavaScript", "C", "Cpp"];
pastebin.spec.ts
:
import { Pastebin } from './pastebin'; describe('Pastebin', () => { it('should create an instance of Pastebin', () => { expect(new Pastebin()).toBeTruthy(); }); it('should accept values', () => { let pastebin = new Pastebin(); pastebin = { id: 111, title: "Hello world", language: "Ruby", paste: 'print "Hello"', } expect(pastebin.id).toEqual(111); expect(pastebin.language).toEqual("Ruby"); expect(pastebin.paste).toEqual('print "Hello"'); }); });
Setting Up Angular-in-Memory-Web-API
We don't have a server API for the application we are building. Therefore, we are going to simulate the server communication using a module known as InMemoryWebApiModule.
npm install angular-in-memory-web-api --save
Add Services
ng generate service pastebin ng generate service in-memory-data
PastebinService will host the logic for sending HTTP requests to the server.
pastebin.service.ts
import { Injectable } from '@angular/core'; import { Pastebin } from './pastebin'; import { HttpClient, HttpHeaders } from '@angular/common/http'; // import { lastValueFrom } from 'rxjs'; import 'rxjs/add/operator/toPromise'; @Injectable() export class PastebinService { // The project uses InMemoryWebApi to handle the Server API. // Here "api/pastebin" simulates a Server API url private pastebinUrl = "api/pastebin"; private headers = new Headers({ 'Content-Type': "application/json" }); constructor(private http: HttpClient) { } // getPastebin() performs http.get() and returns a promise public getPastebin(): Promise<any> { return this.http.get(this.pastebinUrl) .toPromise() .then(response => response.json().data) .catch(this.handleError); } private handleError(error: any): Promise<any> { console.error('An error occurred', error); return Promise.reject(error.message || error); } }
in-memory-data.service.ts
will implement InMemoryDbService
:
import { InMemoryDbService } from 'angular-in-memory-web-api'; import { Pastebin } from './pastebin'; export class InMemoryDataService implements InMemoryDbService { createDb() { const pastebin:Pastebin[] = [ { id: 0, title: "Hello world Ruby", language: "Ruby", paste: 'puts "Hello World"' }, {id: 1, title: "Hello world C", language: "C", paste: 'printf("Hello world");'}, {id: 2, title: "Hello world CPP", language: "C++", paste: 'cout<<"Hello world";'}, {id: 3, title: "Hello world Javascript", language: "JavaScript", paste: 'console.log("Hello world")'} ]; return {pastebin}; } }
Update app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; //In memory Web api to simulate an http server import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryDataService } from './in-memory-data.service'; import { PastebinService } from "./pastebin.service"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, InMemoryWebApiModule.forRoot(InMemoryDataService), AppRoutingModule ], providers: [PastebinService], bootstrap: [AppComponent] }) export class AppModule { }
Create
Cheatsheet
Operations | Command | Comments |
---|---|---|
New an Angular project | ng new <project_name> |
|
Generate a new class | ng generate class <component name> |
|
Generate a new service | ng generate service <service component name> |
|
Launch Angular project | ng serve or npm start |
|
Launch unit test | ng test or npm test |
|
Launch unit test in specific browser | npm test -- --browsers ChromeNoSandboxHeadless or ng test --browsers ChromeNoSandboxHeadless |
Prerequistes: You need have ChromeNoSandboxHeadless defined in your karma.conf.js |
Create component without specs | ng g component --skip-tests=true <component name> |
You can refer to Stackoverflow for more solutions. |
Run specific unit test | ng t -- --include "src/**/your_file_name.component.spec.ts" |
|
Run specific unit test with relative path | ng test -- --include "relative_path_of_the_spec.ts " |
I've tried to use ./ start the relative path, it didn't work. So you'd better to use src/.... |