Jasmine and Karma
Unit testing JS Apps using Jasmine and Karma
Updated: 03 September 2023
Jasmine
What and Why
What
-
Javascript testing framework
-
Can be used with different languages and frameworks
-
Javascript, Node
-
Typescript, Angular
-
Ruby
-
Python
Why
-
-
-
Fast
-
Independent
-
Runs via Node or in Browser
-
Behaviour Driven Development
- Attempts to describe tests in a human readable format
Setting up a test environment
For JS
1<!-- Order of tags is important -->2<link rel="stylesheet" href="jasmine.css" />3<script src="jasmine.js"></script>4<script src="jasmine-html.js"></script>5<script src="boot.js"></script>6
7<!-- Sctipts to be tested -->8<script src="main.js"></script>9
10<!-- Test Script -->11<script src="test.js"></script>
For Node
Add Jasmine as a dev dependency
1npm install --save-dev jasmine
Add test command to package.json
1"scripts": { "test": "jasmine" }
Run the test command
1npm test
Writing a test suite and spec
We want to test the following helloWorld
function.
main.js
1function helloWorld() {2 return 'Hello world!'3}
This function, when called should return 'Hello World'
test.js
1describe('suiteName', () => {2 it('specName', () => {3 expect(helloWorld()).matcher('Hello world!')4 })5})
4 functions
-
Define the Test Suite
1describe(string, function) -
Define a Test Spec
1it(string, function) -
Define the Actual Value
1expect(actual) -
Define the Comparison
1matcher(expected)
Setup and Teardown
1let expected = 'Hello World!'
- Initialize variable that are needed for testing
1beforeAll(function)
- Called before all tests in a Suite are run
1afterAll(function)
- Called after all tests in a Suite are run
1beforeEach(function)
- Called before each test Spec is run
1afterEach
- Called after each test Spec is run
Matchers
-
The means of comparing the
actual
andexpected
values -
Return a
boolean
indicating if a test passed or failed -
Jasmine has some pre-built Matchers
1expect(array).toContain(member)2expect(fn).toThrow(string)3expect(fn).toThrowError(string)4expect(instance).toBe(instance)5expect(mixed).toBeDefined()6expect(mixed).toBeFalsy()7expect(mixed).toBeNull()8expect(mixed).toBeTruthy()9expect(mixed).toBeUndefined()10expect(mixed).toEqual(mixed)11expect(mixed).toMatch(pattern)12expect(number).toBeCloseTo(number, decimalPlaces)13expect(number).toBeGreaterThan(number)14expect(number).toBeLessThan(number)15expect(number).toBeNaN()16expect(spy).toHaveBeenCalled()17expect(spy).toHaveBeenCalledTimes(number)18expect(spy).toHaveBeenCalledWith(...arguments) -
Custom matchers can also be defined
Running tests
-
For JS we have 2 options
-
Open
index.html
in a browser -
Run
npm test
Jasmine Demo
The following 3 files
index.html
1<!DOCTYPE html>2<html>3 <head>4 <title>Jasmine Running</title>5 <meta charset="UTF-8" />6 <meta name="viewport" content="width=device-width, initial-scale=1" />7 <link8 rel="stylesheet"9 href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css"10 />11 </head>12 <body>13 <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>14 <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>15 <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>16 <script src="main.js"></script>17 <script src="test.js"></script>18 </body>19</html>
main.js
1function helloWorld() {2 return 'Hello world!'3}4
5function byeWorld() {6 return 'Bye world!'7}8
9function bigNumber(num) {10 return num + 111}
test.js
1describe('Test Suite', () => {2 let hello = ''3 let bye = 'Bye World :('4 let num = 05
6 beforeAll(() => {7 num = 58 })9
10 beforeEach(() => {11 hello = 'Hello world!'12 })13
14 afterEach(() => {15 hello = ''16 console.log('Test Completed')17 })18
19 it('Says hello', () => {20 expect(helloWorld()).toEqual(hello)21 })22
23 it('Says bye', () => {24 expect(byeWorld()).toEqual(bye)25 })26
27 it('Returns a bigger number', () => {28 expect(bigNumber(num)).toBeGreaterThan(num)29 })30})
Karma
What and Why
What
-
Test Runner for Jasmine in Angular
-
Automated running of tests
-
Does not require us to modify our code
-
Can test on multiple browser instances at once
Testing Angular Components
-
ng generate component <component>
will output:component.html
component.css
component.ts
component.spec.ts
-
Test against an instance of a component
-
Using the Angular Test Bed
Angular Test Bed
- Test behaviour that depends on Angular framework
- Test Change and Property Binding
- Import the
TestBed
,ComponentFixture
and Component to be tested
1import { TestBed, async } from '@angular/core/testing'2import { AppComponent } from './app.component'
-
Configure the Test Bed’s Testing Module with the necerssary components and imports in the beforeEach
-
Reinstantiate the component before each test
1describe('AppComponent', () => {2beforeEach(async(() => {3TestBed.configureTestingModule({4declarations: [5AppComponent6],7}).compileComponents();8}));9...10}); -
Create a fixture and Component Instance
-
wrapper for a Component and Template
1const fixture = TestBed.createComponent(AppComponent)2const app = fixture.debugElement.componentInstance
-
-
If a component has injected dependencies we can get these instances by:
1const service = TestBed.get(ServiceName)
Looking at the App Component
app.component.html
1<div style="text-align:center">2 <h1>Welcome to {{ title }}!</h1>3</div>4<h2>Here are some links to help you start:</h2>5<ul>6 <li>7 <h2><a target="_blank" rel="noopener" href="...">Tour of Heroes</a></h2>8 </li>9 <li>10 <h2><a target="_blank" rel="noopener" href="...">CLI Documentation</a></h2>11 </li>12 <li>13 <h2><a target="_blank" rel="noopener" href="...">Angular blog</a></h2>14 </li>15</ul>
app.component.ts
1import { Component } from '@angular/core'2
3@Component({4 selector: 'app-root',5 templateUrl: './app.component.html',6 styleUrls: ['./app.component.css'],7})8export class AppComponent {9 title = 'app'10}
app.component.spec.ts
1import { TestBed, async } from '@angular/core/testing'2import { AppComponent } from './app.component'3describe('AppComponent', () => {4 beforeEach(async(() => {5 TestBed.configureTestingModule({6 declarations: [AppComponent],7 }).compileComponents()8 }))9 it('should create the app', async(() => {10 const fixture = TestBed.createComponent(AppComponent)11 const app = fixture.debugElement.componentInstance12 expect(app).toBeTruthy()13 }))14 it(`should have as title 'app'`, async(() => {15 const fixture = TestBed.createComponent(AppComponent)16 const app = fixture.debugElement.componentInstance17 expect(app.title).toEqual('app')18 }))19 it('should render title in a h1 tag', async(() => {20 const fixture = TestBed.createComponent(AppComponent)21 fixture.detectChanges()22 const compiled = fixture.debugElement.nativeElement23 expect(compiled.querySelector('h1').textContent).toContain(24 'Welcome to app!'25 )26 }))27})
Running tests with the AngularCLI
ng test
- Other Angular features can also be tested
- Services
- Components
- Classes
- Forms
- Routing
- Dependency Injection
- etc.
Karma Demo
Conclusion
- Jasmine is a relatively simple testing tool
- Easy to implement on a variety of projects
- Karma Automates testing
- Test on multiple places simultaneously
- Well incorporated into Angular