Unit Testing in Angular 4 Using Jasmine and Karma - Part 1
Learn about the logistics and necessity of unit testing and learn how Jasmine and Karma work together to accomplish this in Angular 4 applications.
Join the DZone community and get the full member experience.
Join For FreeIn today’s software development process, one of the key measures or milestones is unit testing. Actually, unit testing is a level of software testing where we can perform any type of testing against each and every individual unit or component of our program or software. Because this type of testing can be executed through an automated process which inputs data for the individual unit or components of the program through a proper data source like excel file, JSON format or any other source and we can check the logic or output of the units or programs before delivering to the actual users.
Unit testing can be possible in any language. To perform unit testing, we need to develop a separate program which basically executes each and every unit of the software independently, providing proper input data from the source and then checking the output result against the expected results. Normally, a unit testing program is written with the programming language in which the actual program is developed; if we develop a program in C#, then we need to develop its related unit testing program in C#, also.
In this article, we will discuss how can we develop a unit testing platform along with coding to check the logical operations of any Angular Components. The Angular Framework has been designed with testability as a primary objective.
JavaScript has become one of the most popular programming languages to build and empower frontend/web application development. We can use JavaScript to develop simple or complex applications. However, applications in production are often vulnerable to bugs caused by design inconsistencies, logical implementation errors, and similar issues. For this reason, it is usually difficult to predict how applications will behave in real-time environments, which leads to unexpected behavior, non-availability of applications, or outages for short or long durations. This generates lack of confidence and dissatisfaction among users. Also, a high cost is often associated with fixing production bugs. Therefore, there is a need to develop applications that are of a high quality and that offer high availability.
To tackle the above scenario, we can follow TDD, or Test Driven Development, to develop the software or program. Test Driven Development is an engineering process in which the developer writes an initial automated test case that defines a feature, then writes the minimum amount of code to pass the test and eventually refactors the code to acceptable standards. When we talk about testing in Angular, we basically point out two different types of testing.
Unit Testing
A unit test is used to test individual components of the system. That’s why unit testing is basically called isolated testing. It is the best practice of testing small isolated pieces of code. If our unit testing depends on external resources like databases, networks, and APIs, then it is not a unit test.
An integration test is a test process which tests the system as a whole and how it will run in production. Basically in Integration test all the small units or component has been logically combined and test that as a whole group. Unit tests should only verify the behavior of a specific unit of code. If the unit's behavior is modified, then the unit test will be updated as well. Unit tests should not make assumptions about the behavior of other parts of your code or your dependencies. When other parts of your code are modified, your unit tests should not fail. (Any failure indicates a test that relies on other components and is therefore not a unit test.) Unit tests are cheap to maintain and should only be updated when the individual units or programs are modified. For TDD in Angular, a unit is most commonly defined as a class, pipe, component, or service. It is important to keep units relatively small. This helps you write small tests which are "self-documenting," easy to read and understand.
Functional Testing
Functional testing is defined as the testing the complete functionality or functional flow of an application. In case of web applications, this means interacting the different UI of the web applications as it is running by the user in the browser in real life. This is also called End to End Testing (E2E Testing).
The Testing Toolchain
Our testing toolchain will consist of the following tools, which can perform unit testing on Angular Framework:
Jasmine
Karma
Phantom-js
Istanbul
Sinon
Chai
In this article, we will use Jasmine and Karma to perform unit tests in Angular 4.
Jasmine
Jasmine is the most popular JavaScript testing framework in the Angular community. This testing framework supports a software development practice which is called Behavior Driven Development or BDD. This is one the main features of Test Driven Development (TDD). This is the core framework that we will write our unit tests with. Basically, Jasmine and BDD try to describe a test method case in a human-readable pattern so that any user, including a non-technical person, can identify what is going on. Jasmine tests are written using JavaScript functions, which makes writing tests a nice extension of writing application code. There are several Jasmine functions in the example, which I have described below:
Name |
Descriptions |
describe |
This method is used to represent a group with related test blocks. This method needs to execute with two arguments –
|
beforeEach |
This method is fired before each test block. |
it |
This method executes a function to perform a test operation. |
expect |
This method evaluates the result from the test block and performs the asserts statements. |
toEqual |
This method is used to compare the expected result and the actual result. |
beforeAll |
This method is executed only once in the test block to provide the description of the test suites. |
The basic sequence to pay attention to is that the function executes a test function so that they expect and toEqual functions can be used to assess the result. The toEqual function is only one way that Jasmine can evaluate the result of a test. I have listed the other available functions below.
Name |
Descriptions |
expect(x).toEqual(val) |
Match the expected and actual result of the test. |
expect(x).toBe(obj) |
Match or Asserts that expected and actual objects are same. |
expect(x).toMatch(regexp) |
Match the expected result is same according to the given regular expression. |
expect(x).toBeDefined() |
Method is used to check expected result is defined or not. |
expect(x).toBeUndefined() |
Method is used to check expected result is undefined or not. |
expect(x).toBeNull() |
Method is used to check expected result is null or not. |
expect(x).toBeTruthy() |
Method is used to match the expected result is true or not i.e. means expected result is a Boolean value. |
expect(x).toBeFalsy() |
Method is used to match the expected result is false or not i.e. means expected result is a Boolean value. |
expect(x).toContain(y) |
Method is used to match the expected result contains the value of y. |
expect(x).toBeGreaterThan(y) |
Method is used to match the expected result is greater than y. |
expect(fn).toThrow(string); |
Method is used to throw any message from expected result. |
expect(fn).toThrowError(string); |
Method is used to throw any exception error from expected result |
Karma
Karma is a test automation tool for controlling the execution of our tests and what browser to perform them on. It also allows us to generate various reports on the results. For one or two tests, this may seem like overkill, but as an application grows larger and the number of units to test grows, it is important to organize, execute, and report on tests in an efficient manner. Karma is library-agnostic, so we could use other testing frameworks in combination with other tools (like code coverage reports, spy testing, e2e, etc.).
In order to test our Angular application, we must create an environment for it to run in. We could use a browser like Chrome or Firefox to accomplish this (Karma supports in-browser testing), or we could use a browserless environment to test our application, which can offer us greater control over automating certain tasks and managing our testing workflow. PhantomJS provides a JavaScript API that allows us to create a headless DOM instance which can be used to bootstrap our Angular application. Then, using that DOM instance that is running our Angular application, we can run our tests.
Karma is basically a tool which lets us spawn browsers and run Jasmine tests inside of them, which are executed from the command line. This results of the tests are also displayed in the command line. Karma also watches our development file changes and re-runs the tests automatically.
Why Is Unit Testing Required?
Basically, it protects the existing code, which can be broken due to any changes.
Integrates an automatic build process to automate the pipeline of resource publishing at any time.
Clarifies what the code does, both when used as intended, and when faced with deviant conditions. Serves as a form of documentation for your code.
Reveals mistakes in design and implementation. When a part of the application seems hard to test, the root cause is often a design flaw, something to cure now rather than later when it becomes expensive to fix.
It allows us to test the interaction of directives or components with its template URL.
It allows us to easily track change detection.
It also allows us to use and test the Angular Dependency Integration framework.
Filename Conventions for Unit Tests
Each unit test is put into its own separate file. The Angular team recommends putting unit test scripts alongside the files they are testing and using a .spec filename extension to mark it as a testing script (this is a Jasmine convention). So, if you had the component /app/components/mycomponent.ts, then your unit test for this component would be in /app/components/mycomponent.spec.ts. This is a matter of personal preference; you can put your testing scripts wherever you like, though keeping them close to your source files makes them easier to find and gives those who aren't familiar with the source code an idea of how that particular piece of code should work.
Angular Test Bed
Angular Test Bed (ATB) is one of the higher level Angular-only testing frameworks, which allows us to pass data to the test environment and can easily test behaviors that depend on the Angular Framework.
When to Use Angular Test Bed
We need to use the Angular Test Bed process to perform unit testing for the below reasons –
This process allows us to test the interaction of a component or directive with its templates.
It also allows us to easily test change the detection mechanism of the Angular.
It also allows us to test the Dependency Injection process.
It allows us to run tests using NgModule configuration, which we use in our application.
It allows us to test user interactions including clicks and input field operation.
Initial Setup Required for Unit Tests
Step 1
If we want to develop a unit test platform for the Angular framework, we first need to install Node.js on our computers. To install Node.js, Just go to https://nodejs.org/en/ and download the latest Node.js version.
After completing the installation, we need to check that Node.js is properly installed. Open the command prompt and type the below command to check the Node.js version. If it shows the Node.js version, that means it installed properly.
Step 2
After installing Node.js, we need to install TypeScript for AngularJS. If we are using Microsoft Visual Studio, then we need to install the TypeScript version as per the Visual Studio version. Otherwise, we can install TypeScript for Visual Studio Code to develop it. There is another simple process to install TypeScript using command prompts. Open the command prompt and type the below commands (this will install the latest versions of TypeScript on your machine):
npm install -g typescript
In the above command, -g represents the global installation and means you can store your code files any location on your machine.
After installation, we need to check that TypeScript is properly installed or not. For that, run the below command in the command prompt:
tsc --v
Step 3
After TypeScript installation, we need to install one more package called LiteServer. It is basically a lightweight development server which acts as a node server. It serves a web page or application, opens it in the browser, and refreshes the web page automatically when any HTML or script-level changes are done. If you use Microsoft Visual Studio, then you don’t need to install this because Microsoft Visual Studio already does that for you. To install LiteServer, you need to execute the below command:
npm install -g lite-server
To run LiteServer, you need to go to the program folder where your index.html page location using command prompt and run this command:
lite-server
Now that our installation is done, we start to develop our first unit test program on the Angular framework.
Our First Unit Test
To start a unit test, first, we need to configure the environment by creating a folder structure like below.
- The App Folder contains both Angular and unit test specs.
- The Component folder contains all the components which we need to unit test.
The Specs folder contains all the Angular test bed unit test models.
Sample Code of tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2015",
"dom"
],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"../node_modules/@types/"
]
},
"compileOnSave": true,
"exclude": [
"node_modules/*",
"**/*-aot.ts"
]
}
Sample code of systemjs.config.js
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
'app': 'app',
// angular bundles
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
// other libraries
'rxjs': 'npm:rxjs@5.5.3',
'rxjs/operators': 'npm:rxjs@5.5.3/operators/index.js',
'tslib': 'npm:tslib/tslib.js',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
'typescript': 'npm:typescript@2.4.2/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts',
meta: {
'./*.ts': {
loader: 'systemjs-angular-loader.js'
}
}
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);
Sample Code of systemjs-angularjs-loader.js
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
module.exports.translate = function(load){
if (load.source.indexOf('moduleId') != -1) return load;
var url = document.createElement('a');
url.href = load.address;
var basePathParts = url.pathname.split('/');
basePathParts.pop();
var basePath = basePathParts.join('/');
var baseHref = document.createElement('a');
baseHref.href = this.baseURL;
baseHref = baseHref.pathname;
if (!baseHref.startsWith('/base/')) { // it is not karma
basePath = basePath.replace(baseHref, '');
}
load.source = load.source
.replace(templateUrlRegex, function(match, quote, url){
var resolvedUrl = url;
if (url.startsWith('.')) {
resolvedUrl = basePath + url.substr(1);
}
return 'templateUrl: "' + resolvedUrl + '"';
})
.replace(stylesRegex, function(match, relativeUrls) {
var urls = [];
while ((match = stringRegex.exec(relativeUrls)) !== null) {
if (match[2].startsWith('.')) {
urls.push('"' + basePath + match[2].substr(1) + '"');
} else {
urls.push('"' + match[2] + '"');
}
}
return "styleUrls: [" + urls.join(', ') + "]";
});
return load;
};
Sample Code of Style.css
/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* items class */
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected:hover {
background-color: #BBD8DC;
color: white;
}
.items .text {
position: relative;
top: -3px;
}
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected {
background-color: #CFD8DC;
color: white;
}
.items li.selected:hover {
background-color: #BBD8DC;
}
.items .text {
position: relative;
top: -3px;
}
.items .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
Sample code of browser-test-shim.js
(function () {
Error.stackTraceLimit = 0;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
var baseURL = document.baseURI;
baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/';
System.config({
baseURL: baseURL,
// Extend usual application package list with test folder
packages: { 'testing': { main: 'index.js', defaultExtension: 'js' } },
// Assume npm: is set in `paths` in systemjs.config
// Map the angular testing umd bundles
map: {
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
},
});
System.import('systemjs.config.js')
//.then(importSystemJsExtras)
.then(initTestBed)
.then(initTesting);
/** Optional SystemJS configuration extras. Keep going w/o it */
function importSystemJsExtras(){
return System.import('systemjs.config.extras.js')
.catch(function(reason) {
console.log(
'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing without it.'
);
console.log(reason);
});
}
function initTestBed(){
return Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
.then(function (providers) {
var coreTesting = providers[0];
var browserTesting = providers[1];
coreTesting.TestBed.initTestEnvironment(
browserTesting.BrowserDynamicTestingModule,
browserTesting.platformBrowserDynamicTesting());
})
}
// Import all spec files defined in the html (__spec_files__)
// and start Jasmine testrunner
function initTesting () {
console.log('loading spec files: '+__spec_files__.join(', '));
return Promise.all(
__spec_files__.map(function(spec) {
return System.import(spec);
})
)
// After all imports load, re-execute `window.onload` which
// triggers the Jasmine test-runner start or explain what went wrong
.then(success, console.error.bind(console));
function success () {
console.log('Spec files loaded; starting Jasmine testrunner');
window.onload();
}
}
})();
Sample code of Index.html file
<!-- Run application specs in a browser -->
<!DOCTYPE html>
<html>
<head>
<script>document.write('<base href="' + document.location + '" />');</script>
<title>Sample App Specs</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css">
</head>
<body>
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.8"></script>
<script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/proxy.js?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/sync-test.js?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/jasmine-patch.js?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/async-test.js?main=browser"></script>
<script src="https://unpkg.com/zone.js/dist/fake-async-test.js?main=browser"></script>
<script>
var __spec_files__ = [
... specs file name with path details
];
</script>
<script src="browser-test-shim.js"></script>
</body>
</html>
In the above index.html file, _spec_files variables are basically used to store the list of Angular unit test spec file details with a proper folder so that when we run this UI in the browser, it loads spec files one by one and executes them with the help of browser-test-shims.js. In that file, initTesting() is basically initializing the unit test methods one by one.
Now, perform the unit test. We first need to add a simple TypeScript class, which basically performs the basic mathematical operations between two numbers.
Sample code of calculator.woinput.ts
export class CalculatorWithoutInput {
private _firstNumber:number=10;
private _secondNumber:number=20;
private _result : number = 0;
constructor(){}
public addNumbers():number{
this._result = this._firstNumber + this._secondNumber;
return this._result;
}
public subtractNumbers():number{
this._result = this._firstNumber - this._secondNumber;
return this._result;
}
public multiplyNumbers():number{
this._result = this._firstNumber * this._secondNumber;
return this._result;
}
}
Now, create another TypeScript file for defining the specs for the above class file.
Sample code of calculator.woinput.spec.ts
import { CalculatorWithoutInput } from '../component/calculator.woinput';
describe('Calcultor Without Inputs (Basic Class)', () => {
let firstNumber :number = 0;
let secondNumber :number = 0;
let result : number = 0;
let objCaculator : CalculatorWithoutInput;
beforeEach(() => {
this.objCaculator = new CalculatorWithoutInput();
});
afterEach(() => {
this.objCaculator=null;
this.firstNumber=0;
this.secondNumber=0;
this.result=0;
});
it('check number addition', () => {
this.firstNumber=10;
this.secondNumber=20;
this.result=this.firstNumber + this.secondNumber;
expect(this.objCaculator.addNumbers())
.toEqual(this.result);
});
it('check number Subtract', () => {
this.firstNumber=10;
this.secondNumber=20;
this.result=this.firstNumber - this.secondNumber;
expect(this.objCaculator.subtractNumbers())
.toEqual(this.result);
});
it('check number Multiply', () => {
this.firstNumber=10;
this.secondNumber=20;
this.result=this.firstNumber * this.secondNumber;
expect(this.objCaculator.multiplyNumbers())
.toEqual(this.result);
});
});
In the above unit test file, we perform the following steps or operations:
First, we import the Angular Class into the unit test specs file.
We define the describe() as mentioned in above to the test cases.
Then, we define three different test cases using it() for checking the operations of the class objects methods, like addNumbers() or subtractNumbers().
In the above methods, we match the expected result with the give output result with using toEqual().
Now, change the code of the index.html file, as below.
<script>
var __spec_files__ = [
'app/specs/calculator.woinput.spec'
];
</script>
Now, run the index.html file in the browser.
Now, let us change our Calculator Class as below so that we can pass numbers from unit test specs and check the result.
export class Calculator {
private _firstNumber:number=10;
private _secondNumber:number=20;
private _result : number = 0;
constructor(){}
public addNumbers(firstNumber : number, secondNumber : number ):number{
return firstNumber + secondNumber;
}
public subtractNumbers(firstNumber : number, secondNumber : number ):number{
return firstNumber - secondNumber;
}
public multiplyNumbers(firstNumber : number, secondNumber : number ):number{
return firstNumber * secondNumber;
}
}
So, for the above code, we also need to change our unit test specs as below.
Now check the output in the browser.
This is the end of Part 1 of our series on unit test using Angular 4. In Part 2 of this series, we will discuss
How to unit test an Angular 4 component.
How to unit test an Angular 4 directive.
How to unit test an Angular 4 service.
Opinions expressed by DZone contributors are their own.
Comments