

How To Get Started with Unit Tests for Angular
source link: https://www.digitalocean.com/community/tutorials/angular-introduction-unit-testing
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction
If your project was created using the Angular CLI, everything will be ready for you to start writing tests using Jasmine as the testing framework and Karma as the test runner.
Angular also provides utilities like TestBed
and async
to make testing asynchronous code, components, directives, or services easier.
In this article, you will learn about writing and running unit tests in Angular using Jasmine and Karma.
Prerequisites
To complete this tutorial, you will need:
This tutorial was verified with Node v16.2.0, npm
v7.15.1, and @angular/core
v12.0.4.
Step 1 — Setting Up the Project
Your test files are usually placed right alongside the files that they test, but they can just as well be in their own separate directory if you prefer.
These spec files use the naming convention of *.spec.ts
.
First, use @angular/cli
to create a new project:
ng new angular-unit-test-example
Then, navigate to the newly created project directory:
cd angular-unit-test-example
Alongside the app.component
, there will be a app.component.spec.ts
file. Open this file and examine its contents:
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'angular-unit-test-example'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('angular-unit-test-example');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('angular-unit-test-example app is running!');
});
});
Understanding Jasmine
First, a few things that are important to know about Jasmine:
describe
blocks define a test suite and eachit
block is for an individual test.beforeEach
runs before each test and is used for thesetup
part of a test.afterEach
runs after each test and is used for theteardown
part of a test.- You can also use
beforeAll
andafterAll
, and these run once before or after all tests. - You test an assertion in Jasmine with
expect
and using a matcher liketoBeDefined
,toBeTruthy
,toContain
,toEqual
,toThrow
,toBeNull
, … For example:expect(myValue).toBeGreaterThan(3);
- You can do negative assertion with
not
:expect(myValue).not.toBeGreaterThan(3);
- You can also define custom matchers.
TestBed
is the main utility available for Angular-specific testing. You’ll use TestBed.configureTestingModule
in your test suite’s beforeEach
block and give it an object with similar values as a regular NgModule
for declarations
, providers
, and imports
. You can then chain a call to compileComponents
to tell Angular to compile the declared components.
You can create a component fixture
with TestBed.createComponent
. Fixtures have access to a debugElement
, which will give you access to the internals of the component fixture.
Change detection isn’t done automatically, so you’ll call detectChanges
on a fixture to tell Angular to run change detection.
Wrapping the callback function of a test or the first argument of beforeEach
with async
allows Angular to perform asynchronous compilation and wait until the content inside of the async
block to be ready before continuing.
Understanding the Tests
This first test is named should create the app
and it uses expect
to check for the presence of the component with toBeTruthy()
.
The second test is named should have as title 'angular-unit-test-example'
and it uses expect
to check that the app.title
value is equal to the string 'angular-unit-test-example'
with toEqual()
.
The third test is named should render title
and it uses expect
to check the compiled code for the text 'angular-unit-test-example app is running!'
with toContain()
.
In your terminal, run the following command:
ng test
All three tests will run and the test results will appear:
Output
3 specs, 0 failures, randomized with seed 84683
AppComponent
* should have as title 'angular-unit-test-example'
* should create the app
* should render title
All three tests are currently passing.
Step 2 — Building an Example Component
Let’s create a component that increments or decrements a value.
Open app.component.ts
in your code editor and replace the following lines of code with the increment
and decrement
logic:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
value = 0;
message!: string;
increment() {
if (this.value < 15) {
this.value += 1;
this.message = '';
} else {
this.message = 'Maximum reached!';
}
}
decrement() {
if (this.value > 0) {
this.value -= 1;
this.message = '';
} else {
this.message = 'Minimum reached!';
}
}
}
Open app.component.html
in your code editor and replace the content with the following code:
<h1>{{ value }}</h1>
<hr>
<button (click)="increment()" class="increment">Increment</button>
<button (click)="decrement()" class="decrement">Decrement</button>
<p class="message">
{{ message }}
</p>
At this point, you should have revised versions of app.component.ts
and app.component.html
.
Step 3 — Building the Test Suite
Revisit app.component.spec.ts
with your code editor and replace it with these lines of code:
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let debugElement: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
debugElement = fixture.debugElement;
}));
it('should increment and decrement value', () => {
fixture.componentInstance.increment();
expect(fixture.componentInstance.value).toEqual(1);
fixture.componentInstance.decrement();
expect(fixture.componentInstance.value).toEqual(0);
});
it('should increment value in template', () => {
debugElement
.query(By.css('button.increment'))
.triggerEventHandler('click', null);
fixture.detectChanges();
const value = debugElement.query(By.css('h1')).nativeElement.innerText;
expect(value).toEqual('1');
});
it('should stop at 0 and show minimum message', () => {
debugElement
.query(By.css('button.decrement'))
.triggerEventHandler('click', null);
fixture.detectChanges();
const message = debugElement.query(By.css('p.message')).nativeElement.innerText;
expect(fixture.componentInstance.value).toEqual(0);
expect(message).toContain('Minimum');
});
it('should stop at 15 and show maximum message', () => {
fixture.componentInstance.value = 15;
debugElement
.query(By.css('button.increment'))
.triggerEventHandler('click', null);
fixture.detectChanges();
const message = debugElement.query(By.css('p.message')).nativeElement.innerText;
expect(fixture.componentInstance.value).toEqual(15);
expect(message).toContain('Maximum');
});
});
We assign the fixture
and debugElement
directly in the beforeEach
block because all of our tests need these. We also strongly type them by importing ComponentFixture
from @angular/core/testing
and DebugElement
from @angular/core
.
In our first test, we call methods on the component instance itself.
In the remaining tests, we use our DebugElement
to trigger button clicks. Notice how the DebugElement
has a query
method that takes a predicate. Here we use the By
utility and its css
method to find a specific element in the template. DebugElement
also has a nativeElement
method, for direct access to the DOM.
We also used fixture.detectChanges
in the last 3 tests to instruct Angular to run change detection before doing our assertions with Jasmine’s expect
.
Once you have made your changes, run the ng test
command from the terminal:
ng test
This will start Karma in watch mode, so your tests will recompile every time a file changes.
Output
4 specs, 0 failures, randomized with seed 27239
AppComponent
* should increment value in template
* should increment and decrement value
* should stop at 0 and show minimum message
* should stop at 15 and show maximum message
All four tests will be passing.
Conclusion
In this article, you will learn about writing and running unit tests in Angular using Jasmine and Karma. Now that you know about the main Angular testing utilities and can start writing tests for simple components.
Continue your learning with testing components with dependencies, testing services as well as using mocks, stubs, and spies.
You can also refer to the official documentation for an in-depth Angular testing guide.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK