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.
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.
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!');
});
});
First, a few things that are important to know about Jasmine:
describe
blocks define a test suite and each it
block is for an individual test.beforeEach
runs before each test and is used for the setup
part of a test.afterEach
runs after each test and is used for the teardown
part of a test.beforeAll
and afterAll
, and these run once before or after all tests.expect
and using a matcher like toBeDefined
, toBeTruthy
, toContain
, toEqual
, toThrow
, toBeNull
, … For example: expect(myValue).toBeGreaterThan(3);
not
: expect(myValue).not.toBeGreaterThan(3);
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.
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:
Output3 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.
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
.
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.
Output4 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.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!