Hi All, I'm Dinanath Jayaswal, Senior UI/Web Developer and Adobe Certified Expert Professional
, I wanna welcome you to Angular Unit Testing with Jasmine and Karma
. In this course/tutorial, will take you from the basics/ground and gives you a solid foundation to write automated tests for your Angular apps. Whether you're an absolute beginner or have some familiarity with automated testing, this course/tutorial will give you all the necessary building block skills to write automated tests for your Angular apps.
Let us dive into a topic which lots of developers/technical professionals are struggling and afraid of - Testing our code
. In this section, we will learn how to write an automated test for Angular Applications.
This Course/Tutorial is ideal for:
- Freshers and aspiring UI (JavaScript/Angular) developers
- Absolute beginner Angular developers (Front-End developer)
- Experienced Technical developers
- Developers upgrading from AngularJS 1.x to Angular 2 or above
- Full Stack Developers
- Technical/Team Leads
- Architects, Technical Project Managers, QAs
This course/Tutorial is for anyone and everyone, Almost everyone! Fresher/Newcomer as well as experienced UI/frontend/Web Developers who are interested in boost skills and further career in Angular world - by learning new latest dynamic tools/utilities which helps to Test Angular application code to become a hi-tech developer.
- Writing Unit/Integration Test cases and testing code is an art, it helps to produce a product with fewer bugs and better quality
- Tests are the best way to prevent software defects
- In the long run for large applications, manual testing ends up with extreme pain and time-consuming. With automated testing, you spend less time fixing bugs and testing code
- Automated testing is a practice that has been adopted by various organizations and a lot of successful software development teams so Unit Testing becomes a popular skill to learn, know and use
After completing/attending this Course/Tutorial, participants should be able to:
- Write Unit and Integration test cases for Angular application using Jasmine
- Write clean and maintainable tests for your Angular apps
- Feel comfortable writing Angular tests for testing Functions, Logics, Events, multiple types of Components, Attributes-Directives, Dependencies, Routers- Navigation, and services
- Know and get familiar with Angular testing best practices
The primary focus for this tutorial is testing Angular Applications and code so it is must to know Angular (version 2 and above) and TypeScript. It is advisable to view course/tutorial on Angular - Angular step by step and Typescript Typescript tutorial for all before dive deeper with Angular testing.
-
Introduction to Automated testing
- 1.1. Common questions in mind of developers
- 1.2. Developers thoughts
- 1.3. What is automated testing?
- 1.4. Is it a replacement for manual testing?
- 1.5. Codebase to develope
- 1.6. Why Test?
- 1.7. Benefits/Advantages of Automated Testing
- 1.8. Do I need Automated testing?
- 1.9. First code or write the test? TDD BDD
- 1.10. What to test?
-
The Angular Testing Tools-Toolchain
- 3.1. Testing Tool categories
- 3.2. Testing Tools
-
Working with Integration Testing
- 8.1. Integration Test-Setup
- 8.2. Integration Test-Generating setup code
- 8.3. ATB Angular TestBed
- 8.4. Integration Test-Property binding
- 8.5. Integration Test-Event binding
- 8.6. fixture detectChanges
- 8.7. Integration Test-Providing dependencies
- 8.8. Testing OnInit ngOnInit
- 8.9. Why do we use NO ERRORS SCHEMA
- 8.10. Why do we use NO_CUSTOM_SCHEMA
- What is automated testing?
- Is it a replacement for manual testing?
- Do I need Automated testing?
- Do I write Test first (TDD - Test Driven Development) or application code first?
- What to test?
Testing our code is an important and integral part of any project
but the majority of developer/technical professionals doesn't follow it, because:
- Testing looks complex
- Its time consuming
- Developers are not clear about Testing
- Don't know how to work with Testing
- No clear idea/picture what to test
Automated testing
- Testing is the process of checking the code/functionality manually or in an automated fashion
- Automated testing is
process/practice of writing code to test our code
, then run tests in an automated fashion - Automated testing is performed by writing test cases/scripts
Manual Testing
is performed by a human carefully executing the test stepsAutomation Testing
means using an automation tool to execute your test case suite
Both Manual Testing & Automated Testing have their advantages and disadvantages:
- For
smaller applications
, Manual Testing is beneficial at the same time Large enterprise applications
Automated Testing plays an important role- Some project/software application (in which requirements changes frequently, R & D Projects) Manual Testing is the best option
- Also in some project/software application combination of both Manual Testing & Automated Testing works and fit fine (
complex things checked manually and dynamic things can be automated tested
)
- Code - We Develop some code/features
- Results - We have some expected results in mind
- Test - We Test/check applicaton
- If all looks good/works fine -
success confirmation
- develope other next piece code/feature - If any error/not happy with code -
Failure
- Modify/Fix and test again
- Code - We Develop some code/features
- Results - We have some expected results in mind
- Test - We can Automate and Simply Tests
- We can test some part manually
- At the same time write test cases/automate some task to avoid manual and time-consuming interaction
Automate testing helps to get breaking changes/last-minute code shocking changes at the initial level itself
Whenever we develop any application we need to test it. Sometimes the test is performed manually by developer/QA team member or at a time we prefer writing an Automated Unit Test.
Test-Driven Development is a single powerful tool to prevent bugs, defects from within our application
. By putting some efforts on Testing we get better quality software with fewer bugs which is more maintainable in the long term.
- Launch application in the browser
- Login to application
- Go to the target page
- Follow proper steps and do some clicks here there to test & check, code or functionality developed
- Test/check positive and negative both scenarios for particular functionality/function
All the way manual testing is pretty time consuming, to follow all the steps/cycle discussed above, we may need a couple of minutes.
- Manual Testing of all workflows, all fields, all negative scenarios is time and money consuming
- It is difficult to test for multi-lingual sites manually
- Manual Testing can become boring and hence error-prone.
We can write code, directly call the function with different positive and negative input values, and test functionality/function in a fraction of the moment.
- Automation increases the speed of test execution
- Automation helps increase Test Coverage
- Automation does not require human intervention. You can run automated test unattended (overnight)
With Automated testing, we have to write Production Code as well as Unit Test Code
, so it takes significantly more time
as compared to normal development time.
Image - Codebase to develop (Production + Test Code)
Its fact that implementing the feature with unit test/testing will take more time than development without unit test/testing.
Image - Production development cost
- In any application at the initial development level, Manual Testing takes less time
- As the application grows with more and more features, functionality and complexity, time is taken by Manual Testing increases exponentially-significantly
- In the longer run, many new team members are not aware of exact functionality and requirements to test as an actual old developer who developed the features are no more working with the company
- At the initial development level, Automated Testing takes more time
- In longer time over the years as and when the application grows with features and complexity, Automated Testing time decreases and it is far lesser than Manual Testing
- Now a days many companies avoid manual testing and automate everything and prefer test automation
Image - Manual vs Automated testing cost
Testing is important and done to:
- Get an error if code break
- Avoid last-minute shocking/breaking code changes
- Tests are the best way to prevent software defects
- Avoid any invalid/bad/unwanted code
- Think about possible issues and bugs in code/application
- Integrate into build workflow (If all test cases succeed than merge code and deployed automatically)
- Save time in the longer run
- Break up complex code dependencies and make our code easier
- Improve code/better code quality
It helps to catch defects/bugs before releasing your application/product/software
- With Automated Testing, we produce software with fewer bugs and of better quality and more reliable
- Tests prevent software defects
- It will help you become a better developer
- It will enforce you to write better and more reliable code
- Helps in regression testing (Testing complete application in chunks, make sure old functionalities are working fine)
- Reveals mistakes in Design/Development (If some features are difficult/complex to write test cases - simplify functionality and logic)
- In the longer run, Automated Unit Testing acts as documentation fo application functionality
- Automated testing has lots of advantages-benefits but it is not good-fit for each project and every team
Needed disciplined code and programmer
in team else we need to spend more time and money to fix broken code which may result in loss- In the Real world, we have constrained for TIME and MONEY - our job is to build + deliver real working software by solving problems and deliver values to the world
Depends on the Project Budget, Development Time and Resources we can decide to go for Automated Testing or not
- A startup who have limited budget and time
- Startup companies who are not sure about product future
- Companies who develop research-based/experimental products (R & D projects)
- Software/application in which requirements changes frequently
-
TDD (Test Driven Development)
- Sometimes, we write tests before we even start developing which is called TDD (Tests are written before the code)
- We mostly follow BDD (Behavior Driven Development) since we are using the Jasmine framework
-
Behavior Driven Development
- Behavior Driven testing is an extension of TDD
- Like TDD in BDD also we can write tests first and then add application code, The major difference that we get to see here are:
Tests are written in plain descriptive English type grammar
- Tests are explained as a behavior of an application and are more user-focused
- Using examples to clarify requirements
This difference brings in the need to have a language which can define, in an understandable format.
Its all depends on TIME, MONEY and need/requirements - usually, developers and companies first prefer to do application development then go for testing ie. BDD (Behavior Driven Development).
Test cases to be automated can be selected using the following criterion to increase the automation ROI:
- High Risk - Business Critical test cases
- Test cases that are repeatedly executed
- Test Cases that are very tedious or difficult to perform manually
- Time-consuming test Cases
We can write Angular tests cases for testing Functions, Logics, Events, multiple types of Components, Attributes-Directives, Dependencies, Routers- Navigation, and services.
While writing test we must think/test all execution scenarios like if and else condition, positive, negative outputs, pass and fail cases, true or false switch case
etc.
The following category of test cases are not suitable for automation:
- Test Cases that are newly designed and not executed manually at least once
- Test Cases for which the requirements are frequently changing
- Test cases which are executed on an ad-hoc (emergency or when needed) basis
There are different ways/types to test our application code. In Angular context, there are following types of test: isolated and shallow unit testing (without any custom tags), integration tests between components and UI/E2E tests, which can be functional and visual regression testing
. In general, we have 3 types of tests as given below:
- Unit Test
- Integration Test
- End-to-end Test (Functional Testing)
Let's go through each testing type in detail and write the right tests for ensuring the application/testing work as expected.
Testing a function in isolation (testing one function, test individual components of the system)
- This is sometimes also called
Isolated testing
, It’s the practice oftesting small isolated pieces of code
- Test an individual component
in isolation, without external resources
(e.g. file system, server, database, API endpoints, etc.) - In Angular terms/context
testing only component class
file without any template/view, with fake services and fake routers - Coverage - Small Unit/chunk of code to test
- Complexity - Easier to write
- Time/Duration - Super Fast, takes less time
- Frequency - Write more/as much as you can (Hundreds)
- Funtionality Testing Confidence - Does not test the functionality of the application in detail, not give us much confidence about functionality
Note:
- To perform unit testing for application code, we need to
develop a separate program which executes each unit of the software independently
, providing proper input data from the source and then checking the output result against the expected results - Usually, a unit testing program is written with the same programming language in which the actual program/code-logic is developed; e.g. if we develop a program in C#/Java/JavaScript, then we need to develop its related unit testing program in C#/Java/JavaScript respectively
Image - Unit Testing
If any Unit testing depends on external resources like databases, networks, and APIs, then it is not a unit test, it's an Integration Test
- Testing a Component with Template interaction, dependencies (testing a function that calls and depends on another function)
- In Angular terms/context
testing component along with template (component + template)
, with fake services and fake routers - Coverage - Test a component
with external resources
(e.g. file system, server, database, API endpoints etc.) - Complexity - Little complex as we need to deal with dependency injection
- Time/Duration - Little time consuming
- Frequency - Write a good couple of (Tens)
- Funtionality Testing Confidence - Give us much confidence about functionality
Image - Integration Testing
Test an entire application as a whole including function, user interaction, service API calls and complete functionality
- This is defined as the testing of the complete functionality of an application
- End-to-End or Functional Testing simply means interacting with your application as it’s running in a browser just like a user would interact with it in real life, i.e. via clicks on a page, filling up data, etc.
Testing and checking application from launching in browser till individual functionality, Full flow (validating a DOM after a click)
- Coverage - Test whole app functionality, get more confidence about functionality
- Complexity - Complex
- Time/Duration - Pretty time consuming, Tests are very slow
- Frequency - Write a few (1,2 or so)
- Funtionality Testing Confidence - Very fragile (can get broken easily) if any changes in template or component
The testing thumb-rule/testing pyramid says that: 75-80 % of the tests should be unit tests, 15-20 % is integration tests and 5 % is End-2-End UI tests
, overall depends on companies to companies, requirements to requirements.
Note: Write more Unit and Integration Test, few/less End-to-End tests only for key functionalities
Angular testing toolchain consist of various tools of different types/categories, which can perform unit testing on Angular Framework. Some of the tools categories mentioned below:
- Assertion Library:
- Define test cases, write testing logic, conditions
- Examples/Tools: Jasmine, Chai
- Test Runner:
- Execute our tests and summarize results
- Examples/Tools: Karma, Mocha
- End-to-End testing tools:
- Perform complete Application test
- Examples/Tools: Protractor
- Headless Browser:
- Simultes browser interaction, execute tests in GUI-less browser
- Examples/Tools: Headless chrome, Puppeteer, PhantomJS
We can test our Angular applications from scratch by writing and executing pure javascript functions. Creating instances of the relevant classes, calling functions and checking the actual versus expected result.
But as testing is such a common activity with javascript there are several testing libraries/frameworks available to use which reduce the amount of time it takes to write tests.
Here is the list of important tools used for Angular testing:
- Jasmine
- Karma
- Protractor
- PhantomJS
- Istanbul
- Chai
- Sinon
- Mocha
- Angular Testing Utilities
Jasmine is an open-source Behavior Driven Development (BDD) framework for testing JavaScript code, BDD is an important feature of Test Driven Development (TDD)
- Jasmine is the most popular JavaScript testing framework in the Angular community - core framework to write a unit test
- Angular is built-in with Jasmine and Karma so it's pretty easy to get started with testing
- Jasmine provides bunch of functions to write test, e.g.
describe()
,it()
etc. - Dependency free and does not require a DOM (Document Object Model)
- Website URL: https://jasmine.github.io/
A test runner tool for running and executing unit test while developing an Angular app
- Karma is a direct product of the AngularJS team and default test runner for applications created with the Angular CLI
- Shows the
outcome of all test
run whether to pass or fail - Karma allows us to
generate various reports on the results
- Increases developer productivity by showing/giving live results of tests
- Karma is a tool which let's use
Chrome, Firefox or provides headless browsers
and run Jasmine tests inside of them - Website URL: https://karma-runner.github.io/latest/index.html
Protractor is an End-to-End/End-2-End (E2E) behavior-driven testing framework for Angular
- It helps QA or Testers to write and run End-to-End/End-2-End (E2E) to test complete application flow
- It Explores the app as users experience it
- Website URL: https://www.protractortest.org/#/
PhantomJS is a headless WebKit scriptable browser with a JavaScript API
- It has fast and native support for various web standards like DOM handling, CSS selector, JSON, Canvas, and SVG
- PhantomJS - Scriptable Headless Browser, in simple terms we can say - PhantomJS is a web browser
without a graphical user interface
- PhantomJS can be used to take screenshots of websites, those screenshots can be rendered in different formats also
- As we can use PhantomJS to load and manipulate a web page, it is perfect for carrying out page automation. This helps developers run a bunch of tests without ever having to open a web browser
PhantomJS development is suspended until further notice, PhantomJS is a discontinued headless browser used for automating web page interaction
- Website URL: https://phantomjs.org/
Angular uses Istanbul and Karma for built-in testing
Istanbul is a Karma reporter
that uses the latest Istanbul 1.x APIs (with full source map support) to report coverage- Website URL:
Standalone test spies, stubs and mocks for JavaScript (fake service/HTTP-API calls)
- It works with any unit testing framework and has no external dependencies
- Installation command:
npm install sinon
- Jasmine already consists of
spies, Mock
, just a thought from some community that Sinon.JS is better! - Website URL: https://sinonjs.org/
Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework
Chai is an assertion library with some tasty syntax sugar
that can be paired with any other testing framework- Jasmine already uses a TDD style, so developers using Chai for its BDD interfaces, mainly through the use of
should
andexpect
- Installation command:
npm install chai
- Website URL:
A test runner tool for running and executing unit test while developing an Angular app
- Mocha is a testing framework that provides functions that are executed according to in a specific order, and that logs their results to the terminal window
- Mocha makes synchronous and asynchronous testing simple and fun, also runs test serially, allowing for flexible and accurate reporting
- Installation command:
npm install mocha
- Website URL:
- Help us to create a test environment for the complete/whole application code
- Used to cover/test interactions and functionality as a whole
- Angular
TestBed
utility / Angular Test Bed (ATB):- The first and most important inbuilt testing utility which creates an angular testing module
- Used to test interaction between - component and its template or with different component
- Package/Import statement -
import { TestBed } from '@angular/core/testing';
In the current course/tutorial will mainly deal with Jasmine (to write tests) & Karma (to run the test and get results/code coverage reports) with Angular Testbed (ATB - to cover interactions and functionality)
.
To create Angular application we need Node/NPM/Angular CLI
to be installed on the machine, if not then go through following steps and install required tools:
- Install Node (https://nodejs.org/en)
- Install Angular CLI (https://cli.angular.io/) command:
npm install -g @angular/cli
ORnpm i -g @angular/cli
- Create a new Angular project with Angular CLI command:
ng new project/appName
ORng new angular-unit-test-demo
- Run Angular App by using Angular CLI command:
ng serve
- To run the test cases/test scripts command:
ng test
ng test
command read all the spec/test files from an application with.spec
extensions and execute them in serial orderKarma
launches a headless browser window with port9876
OR addresshttp://localhost:9876/?id=96650121
and shows test results and statistics (total test cases run, test pass/fail, etc.)
Note:
- We don’t have to set up anything special to start on unit testing, thanks to
Angular CLI - an Angular Command Line Interface/Utility
- Angular CLI creates all the set up with packages and dependencies (Jasmine and karma) with
.spec test file
for us - Any system which has
Node/NPM/Angular CLI
installed, can simply create an Angular App with the command:ng new appName
and then - run
ng test
command to start running default tests/test script present in the application in the form of.spec
files
Note: For detailed description of Node/NPM/Angular/Angular CLI Setup, please refer the following links:
- https://github.com/dinanathsj29/angularcli-angualr-cli#02-Getting-Started-with-Angular-CLI
- https://github.com/dinanathsj29/angular7-step-by-step#02-getting-started
Image - Angular App Folder Structure - ng serve
Image - Running Angular App - ng serve output
Image - Executing ng test command
Image - Running Angular Test - ng test output
See the image below which lists all the dependencies installed for testing purposes. Let’s go through the more important ones:
Image - Testing packages-dependencies
- jasmine-core:
Jasmine is the framework we are going to use to create our tests (.spec files consists of Jasmine logic)
- It has a bunch of functionalities to allow us the write different kinds of tests
- karma:
Karma is a task runner tool for our tests
- It uses a configuration file to set the startup file, the reporters, the testing framework, the test output browser
- protractor:
End to End test writing framework
- The rest of the dependencies are mainly reporters for our tests, different tools to use karma and Jasmine and browser launchers
Here are some of the core test settings/configuration files
- karma.conf.js:
- The unit test runner configuration file
- protractor.conf.js:
- Protractor end-to-end test framework configuration file
- test.ts:
- The main entry point for unit tests and loads all the
.spec
and framework files - The angular-cli configuration of karma uses the file
test.ts
as the entry point of the tests for the application - This file is required by karma.conf.js and loads recursively all the .spec and framework files
- The main entry point for unit tests and loads all the
- Need to
follow clean coding practices
for writing code of development as well as test cases - Small functions/methods (10 lines of code or less)
- Proper naming conventions for variables and functions/methods (Follow clear and consistent naming conventions for your unit tests)
- Single responsibility principle (one function should do/perform one task only)
- Unit Test cases should be independent (In case of any enhancements or change in requirements, unit test cases should not be affected)
- Test only one code at a time
- While writing test we must think/test all execution scenarios like
if and else condition, positive, negative outputs, pass and fail cases, true or false switch case
etc. - In case of a change in code in any module, ensure there is a corresponding unit Test Case for the module, and the module passes the tests before changing the implementation
- Bugs identified during unit testing must be fixed before proceeding to the next phase in SDLC (Software Development Life Cycle)
- Adopt a
test as you code
approach, The more code you write without testing, the more paths you have to check for errors
- Create a new Test file which should have
.spec.ts
extension- Every class/component file should have its corresponding Unit Test case file with
.spec.ts
extension (e.g.: component = header.component.ts, unit testing/spec file = header.component.spec.ts)
- Every class/component file should have its corresponding Unit Test case file with
- Angular development team recommends putting/keeping unit test scripts alongside the files they are testing and using a
.spec
filename extension to mark it as a testing script (it's a Jasmine convention)header.component.ts, header.component.spec.ts
- should present under one folder
- Inside
.spec
test file write Unit Test cases as per logic/functionality mentioned and/or as per specific needs-requirements in the component class file - To run the test cases/test scripts, use Angular CLI command:
ng test
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('Textual description of the group', Function for Jasmine to invoke that will define inner suites specs () => {
// 2. it - define an individual unit test case
it('expectation from current test case', () => {
// 3. expect - Create an expectation/assertion for a spec, expect().matcherFunction();
expect(expectation).toBe(expected);
})
})
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('HeaderComponent', () => {
// 2. it - define an individual unit test case
it('should have header title', () => {
// 3. expect - Create an expectation for a spec/assertion, expect().matcherFunction();
let title = 'Welcome to Angular Testing';
expect(title).toEqual('Welcome to Angular Testing');
})
})
Syntax & Example:
function.ts:
function sayHello() {
return 'Hello World!!!';
}
Syntax & Example:
Jasmine test .spec.ts:
describe('Say Hello', () => {
it('should says Hello World', () => {
expect(sayHello())
.toEqual('Hello World!!!');
});
});
- describe()
- it()
- expect()
- Various Matcher functions
describe() function block define a test suite (the group of related tests)
- The
describe(string, function)
function defines what we call aTest Suite
, a collection of individualTest Specs
- Usually, with describe block we define/describe - the name of the current component to test
- e.g.
describe('test-suite-name / component-name', () => {
})
OR
describe('LoginComponent', () => {
})
it() block define a individual spec or test
- The
it(string, function)
function defines an individualTest Spec
, this contains one or moreTest Expectations
- The
it
block must have proper readable and meaningful statement as a Test Spec/description - With in
describe()
, we canhave one or more it() functions
- (group of related test/sepc) - Inside
it()
blockdefine test/code behavior
- expectation/assertion - e.g.
it('test-spec-name', () => {
})
OR
it('should have/show submit button', () => {
})
expect() function block is Jasmine API for checking expectation/assertion
- The
expect(actual)
expression is what we call an Expectation - Takes actual value as the parameter
- Chained with a Matcher function
- In conjunction with a
Matcher Function,
it describes an expected piece of behavior in the application - e.g.
expect(something).matcherFunction(value expected)
OR
expect(var-name).toBe(value);
expect(var-name).toContain(value);
expect(var-name).toEqual(value);
expect(var-name).toBeNull(value);
expect(var-name).toBeTruthy(value);
expect(var-name).toBeFalsy(value);
The matcher(expected) expression is what we call a Matcher
- It does a boolean comparison with the
expected
value passed in VS. theactual
value passed to theexpect
function if they are false the spec fails - It is responsible for reporting to Jasmine if the expectation is true or false, Jasmine will then pass or fail the spec/test
- Jasmine comes with a rich set of pre-built matchers like:
expect(array).toContain(member);
expect(fn).toThrow(string);
expect(fn).toThrowError(string);
expect(instance).toBe(instance);
expect(mixed).toBeDefined();
expect(mixed).toBeFalsy();
expect(mixed).toBeNull();
expect(mixed).toBeTruthy();
expect(mixed).toBeUndefined();
expect(mixed).toEqual(mixed);
expect(mixed).toMatch(pattern);
expect(number).toBeCloseTo(number, decimalPlaces);
expect(number).toBeGreaterThan(number);
expect(number).toBeLessThan(number);
expect(number).toBeNaN();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledTimes(number);
expect(spy).toHaveBeenCalledWith(...arguments);
Till now we know and learned that Unit testing is nothing but testing only component class
file without any template/view or external dependencies, with fake services and fake routers. so its time to learn basics-fundamentals to advanced level of unit testing
As and when we are running the test, it should run in an isolated mode - as its only test exist in the .spec.ts file
- Comment/Remove old test cases (app.component.spec.ts) - To avoid any confusion/conflicts with pre-existing tests, please comment/remove all the test cases (3 tests) available in
app.component.spec.ts
file.
- Create a new folder and first component file -
app/01-unit-test/01-fundamentals-functions/01-counter.ts
Syntax & Example:
01-fundamentals-functions/01-counter.ts
export class CounterComponent {
public counter: number = 0;
// increment
public increaseCounter(): number {
this.counter++;
return this.counter;
}
// decrement
public decreaseCounter(): number {
this.counter--;
return this.counter;
}
} // class CounterComponent
Image - Unit Testing file/folder - counter component
- Create a .spec/test file -
01-counter.spec.ts
and write test case for following scenarios:- if
increaseCounter
function called thancurCounterValue
value will be greater than zero (positive)
- if
Syntax & Example:
01-fundamentals-functions/01-counter.spec.ts
// import component to test
import { CounterComponent } from "./01-counter";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('CounterComponent', ()=> {
// 2. it - define an individual unit test case
it ('should check incremented value is greater than zero', ()=> {
// dependency injection
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.increaseCounter();
// 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeGreaterThan(0);
})
}) // describe
Image - Unit Testing - counter .spec test file
- Run the test cases/test scripts, by using Angular CLI command:
ng test
Image - Unit Testing - Running test
Image - Unit Testing - Running test - Browser Karma output
- Write another test case for the negative scenario as:
- in case of
decreaseCounter
function executed curCounterValue value will be less than zero (negative)
- in case of
it ('should check decremented value is less than zero', ()=> {
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.decreaseCounter();
expect(curCounterValue).toBeLessThan(0);
})
Syntax & Example: final
01-fundamentals-functions/01-counter.spec.ts
// import component to test
import { CounterComponent } from "./01-counter";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('CounterComponent', ()=> {
// 2. it - define an individual unit test case
it ('should check incremented value is greater than zero', ()=> {
// dependency injection
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.increaseCounter();
// 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeGreaterThan(0);
})
it ('should check decremented value is less than zero', ()=> {
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.decreaseCounter();
expect(curCounterValue).toBeLessThan(0);
})
}) // describe
Image - Unit Testing - Running all tests
Image - Unit Testing - Running all test - Browser Karma output
Syntax & Example:
02-parameterized-functions/01-parameter-counter.ts
export function counterParameter(num): number {
if (num < 0) {
return 0
} else {
return num + 1;
}
}
Syntax & Example:
02-parameterized-functions/01-parameter-counter.spec.ts
// import function/component to test
import { counterParameter } from "./01-parameter-counter";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('counterParameter', ()=> {
// 2. it - define an individual unit test case
it ('should return zero 0 if input is negative', ()=> {
const counterResult = counterParameter(-1);
// 3. expect - Create an expectation/assertion for a spec
expect(counterResult).toBe(0);
expect(counterResult).toBeLessThanOrEqual(0);
})
it ('should increment if input is positive', ()=> {
const counterResult = counterParameter(1);
expect(counterResult).toBe(2);
})
}) // describe
Syntax & Example:
02-parameterized-functions/02-parameter-dimension-calculator.ts
export class DimensionCalculator {
public getArea(length: number, width: number): number {
let area = length * width;
return area;
}
}
Syntax & Example:
02-parameterized-functions/02-parameter-dimension-calculator.ts
// import function/component to test
import { DimensionCalculator } from "./02-parameter-dimension-calculator";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('DimensionCalculator', () => {
// 2. it - define an individual unit test case
it('should return area equal or greater than 100', () => {
// dependency injection
let dimensionComponent: DimensionCalculator = new DimensionCalculator();
const areaResult = dimensionComponent.getArea(10, 10);
// 3. expect - Create an expectation/assertion for a spec
expect(areaResult).toBeGreaterThanOrEqual(100);
})
}) // describe
- The
.spec.ts
test file may contain multiple test cases/test scripts, as in current application01-counter.spec.ts
file has a total 2 test cases. Let's see, learn and deal with required or unwanted test cases:
- To run a specific test from a bunch of tests cases, one can simply put/use/insert
f
beforeit() function block
fit ('should check incremented value is greater than zero ', ()=> { })
- Developer can also focus on specific test suits by pre-pending
f
withdescribe()
:
fdescribe('Say Hello', () => {
fit('should says Hello World', () => {
expect(sayHello())
.toEqual('Hello World!!!');
});
});
Image - Unit Testing - Running specific test
Image - Unit Testing - Running specific test - Browser Karma output
- To disable test put/use/insert
x
beforeit() function block
, it will prevent running that particular specs/test scripts- In the below scenario, 2nd test case with
x
will be ignored/disabled
- In the below scenario, 2nd test case with
it ('should check incremented value is greater than zero ', ()=> { })
xit ('should check decremented value is less than zero ', ()=> { })
- The developer can easily disable test suit or tests without commenting them out but by just pre-pending
x to the describe
orit functions
:
xdescribe('Say Hello', () => {
xit('should says Hello World', () => {
expect(sayHello())
.toEqual('Hello World!!!');
});
});
Note: We can use
f
andx
even withdescribe() block
to run specific test suit or to ignore unwanted test suit
Image - Unit Testing - Ignore specific test
Image - Unit Testing - Ignore specific test - Browser Karma output
Note: What to test?
- Just check availability
.toContain()
of particuler text/string - With Strings and Arrays dont write very specific/fragile unit test case (with .
toBe()
matcher function) - if specific string not availble then it may break easily - instead, its advisable to use
.toContain()
matcher function - to check availability of particuler text/string
Syntax & Example:
04-strings/01-string-greetings.ts
export function greetingsTo(personName: string) {
return 'Welcome ' + personName;
// return 'Hello ' + personName;
}
Syntax & Example:
04-strings/01-string-greetings.spec.ts
// import function/component to test
import { greetingsTo } from "./01-greetings";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('GreetingString', () => {
// 2. it - define an individual unit test case
it('should include person name in the greeting message', () => {
// 3. expect - Create an expectation/assertion for a spec
expect(greetingsTo('Dinanath')).toContain('Welcome Dinanath');
// expect(greetingsTo('Dinanath')).toContain('Hello Dinanath');
})
}) // describe
Note: What to test?
- While testing an array we must need to assert/test/check that array must include-contains particular item/string/value, irrespective of its exact position in an array
Syntax & Example:
05-arrays/01-array-country.ts
export function getCountry() {
return ['India', 'Russia', 'Japan', 'israel', 'France'];
}
Syntax & Example:
05-arrays/01-array-country.spec.ts
// import function/component to test
import { getCountry } from "./01-array-country";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('ArrayGetCountry', () => {
// 2. it - define an individual unit test case
it('should return supported country in array', () => {
// 3. expect - Create an expectation/assertion for a spec
expect(getCountry()).toContain('India');
expect(getCountry()).toContain('Russia');
})
it('should return supported country in array', () => {
const countryResult = getCountry();
expect(countryResult).toContain('India');
expect(countryResult).toContain('Israel');
})
}) // describe
-
Set Up:
- Sometimes to test a feature we need to perform some setup, perhaps it’s creating some test objects, components initialization, dependency injection, etc.
- Code written in
beforeEach()
function is known asSet Up
-
Tear Down:
- Also, we may need to perform some cleanup activities after we have finished testing, like reset object/variable value, perhaps we need to delete some files from the hard drive, etc.
- Code written in
afterEach()
function is known asTear Down
Jasmine has a few functions we can use to make the activities named setup and teardown easier:
-
beforeAll():
- This function is called once, before all the specs in
describe
test suite is run
- This function is called once, before all the specs in
-
afterAll():
- This function is called once
after all the specs in a test suite are finished
- This function is called once
-
beforeEach():
- Jasmine calls this function before each and every test -
it
function - This function is called before each test specification,
it
function, has been run - Its advisable to put
common things/initialize
variables/object insidebeforeEach()
function beforeEach
runs before each test and is used for thesetup
part of a test
- Jasmine calls this function before each and every test -
-
afterEach():
- Jasmine calls this function after every test -
it
function - This function is called after each test specification has been run
- Runs after each test and is used for the
teardown
part of a test
- Jasmine calls this function after every test -
Syntax & Example:
06-setup-and-teardown/01-setup-teardown-beforeall-afterall.ts
export function sayHello() {
return 'Hello World!!!';
}
Syntax & Example:
06-setup-and-teardown/06-setup-and-teardown/01-setup-teardown-beforeall-afterall.spec.ts
// import component to test
import { sayHello } from "./02-setup-teardown-beforeall-afterall";
// 1. describe - define test suite, Create a group of specs (often called a suite)
describe('Say Hello', () => {
let expectedResult = '';
// setup - initialize objects and variables
beforeEach(() => {
expectedResult = 'Hello World!!!';
console.log('setup beforeEach', expectedResult);
})
// teardown - reset object/variable value
afterEach(() => {
expectedResult = '';
console.log('teardown afterEach', expectedResult);
})
// 2. it - define an individual unit test case
it('should says Hello World', () => {
// 3. expect - Create an expectation/assertion for a spec
expect(sayHello())
.toEqual(expectedResult);
})
}) // describe
The AAA (Arrange, Act, Assert) pattern/structure
is a common way of writing unit tests for a method under test. It suggests that you should divide your test method into three sections: arrange, act and assert
. It also makes the test more clean and readable.
-
Arrang:
- Initialize system under test
- The
Arrange
section of a unit test methodinitializes objects
andsets the value
of the data that is passed to the method under test let component = new ComponentToCheck();
-
Act:
- Calling a method/function which perform some activity
component.methodFunction();
-
Assert:
- The fact to check/test
- The
Assert section verifies
that the action of the method under test behaves as expected
expect(var-name).toBe(value);
expect(var-name).toContain(value);
expect(var-name).toEqual(value);
expect(var-name).toBeNull(value);
expect(var-name).toBeTruthy(value);
expect(var-name).toBeFalsy(value);
Syntax & Example:
06-setup-and-teardown/02-counter.ts
export class CounterComponent {
public counter: number = 0;
// increment
public increaseCounter(): number {
this.counter++;
return this.counter;
}
// decrement
public decreaseCounter(): number {
this.counter--;
return this.counter;
}
} // class CounterComponent
Syntax & Example:
06-setup-and-teardown/0201-basic-simple-counter.spec.ts
import { CounterComponent } from "./01-counter";
describe('BasicSimplefCounterComponent', () => {
it('should check incremented value is greater than zero', () => {
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.increaseCounter();
expect(curCounterValue).toBeGreaterThan(0);
})
it('should check decremented value is less than zero', () => {
let counterComponent: CounterComponent = new CounterComponent();
const curCounterValue = counterComponent.decreaseCounter();
expect(curCounterValue).toBeLessThan(0);
})
})
Syntax & Example:
06-setup-and-teardown/0202-aaa-arrange-act-assert-counter.spec.ts
import { CounterComponent } from "./01-counter";
describe('ArrangeActAssertCounterComponent', () => {
it('should check incremented value is greater than zero', () => {
// Arrange - dependency injection
let counterComponent: CounterComponent = new CounterComponent();
// Act - call a method/function
const curCounterValue = counterComponent.increaseCounter();
// Assert - 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeGreaterThan(0);
})
it('should check decremented value is less than zero', () => {
// Arrange - dependency injection
let counterComponent: CounterComponent = new CounterComponent();
// Act - call a method/function
const curCounterValue = counterComponent.decreaseCounter();
// Assert - 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeLessThan(0);
})
})
Syntax & Example:
06-setup-and-teardown/0203-setup-teardown-counter.spec.ts
Note: To follow Setup and TearDown methodology we can remove the common/repetitive code from
it() block
and put in the body of test suit ie.inside describe() block
import { CounterComponent } from "./01-counter";
describe('SetupTearDownCounterComponent', () => {
// Arrange - dependency injection
let counterComponent: CounterComponent;
// setup - initialize objects and variables
beforeEach(() => {
counterComponent = new CounterComponent();
})
it('should check incremented value is greater than zero', () => {
// Act - call a method/function
const curCounterValue = counterComponent.increaseCounter();
// Assert - 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeGreaterThan(0);
})
it('should check decremented value is less than zero', () => {
// Act - call a method/function
const curCounterValue = counterComponent.decreaseCounter();
// Assert - 3. expect - Create an expectation/assertion for a spec
expect(curCounterValue).toBeLessThan(0);
})
})
Note: What to test?
- At the time of
LoginFormComponent initialization
, it must haveFormGroup
with three controlsname
,password
andemail
- Insure
Validataors
for controls should be present/available/used
Syntax & Example:
07-forms/01-loginform.component.ts
import { FormBuilder, FormGroup, Validator, Validators } from "@angular/forms";
export class LoginFormComponent {
loginForm: FormGroup;
constructor(loginFB: FormBuilder) {
this.loginForm = loginFB.group({
name: ['', Validators.required],
password: ['', Validators.minLength(8)],
email: ['', Validators.email]
})
}
}
Syntax & Example:
07-forms/01-loginform.component.spec.ts
import { LoginFormComponent } from "./01-loginform.component";
import { FormBuilder } from "@angular/forms";
describe('LoginFormComponent', () => {
let loginFormComponent: LoginFormComponent;
beforeEach(() => {
loginFormComponent = new LoginFormComponent(new FormBuilder);
})
it('should create a form with 3 controls', () => {
expect(loginFormComponent.loginForm.contains('name')).toBe(true);
expect(loginFormComponent.loginForm.contains('password')).toBeTruthy();
expect(loginFormComponent.loginForm.contains('email')).toBeTruthy();
})
it('should make the name control required', () => {
let nameControl = loginFormComponent.loginForm.get('name');
nameControl.setValue('');
expect(nameControl.valid).toBeFalsy();
})
it('should use password with minimum 8 characters', () => {
let passwordControl = loginFormComponent.loginForm.get('password');
passwordControl.setValue('12345678')
expect(passwordControl.valid).toBeTruthy();
})
it('should validate the email input type', () => {
let emailControl = loginFormComponent.loginForm.get('email');
emailControl.setValue('dinanathj@gmail.com')
expect(emailControl.valid).toBeTruthy();
})
})
Note: What to test?
EventEmitters are observables, so subscribe to them and test/check the emitted event/value
Syntax & Example:
08-event-emitter/01-event-emitter-count.component.ts
import { EventEmitter } from "@angular/core";
export class EventCounterComponent {
totalCount = 0;
counterChanged = new EventEmitter();
incrementCounter() {
this.totalCount++;
this.counterChanged.emit(this.totalCount);
}
}
Syntax & Example:
08-event-emitter/01-event-emitter-count.component.spec.ts
import { EventCounterComponent } from "./01-event-emitter-count.component";
describe('EventCounterComponent', () => {
let eventCounterComponent: EventCounterComponent;
beforeEach(() => {
eventCounterComponent = new EventCounterComponent();
})
it('should raise counterChanged event when incrementCounter fired', () => {
let totalCounter = 0;
// let totalCounter = null;
eventCounterComponent.counterChanged.subscribe(_totalCount => {
totalCounter = _totalCount;
eventCounterComponent.incrementCounter();
expect(totalCounter).toBe(1);
// expect(totalCounter).not.toBeNull();
})
})
})
There are some limitations with unit tests:
Routers
(Not able to cover with Unit Test. we need to load component in Angular environment so need to follow Integration testing)Template interpolation and binding
(With a Unit test, not sure properties bound properly in the template )Event Handlers
(click event or so)- Unit testing
can't be expected to catch every error
in a program - It is
not possible to evaluate all execution paths
even in the most trivial/small programs - Unit testing focuses on a unit of code, hence it
can't catch integration errors
or broad system-level errors
- As and when we write the test for our application we must need to know and understand how much code has been covered under test cases
Code coverage
represents how muchpercent of code is covered with unit tests
- With the Angular CLI, we can run unit tests as well as create code coverage reports - Code coverage reports allow us to see any parts of our code base that may not be properly tested by our unit tests
karma.conf.js
consists of a keycoverageIstanbulReporter
for code coverage related settings:
If your team decides on a set minimum amount to be unit tested you can enforce this minimum with the Angular CLI:
- The
thresholds
property will enforce a minimum of80%
code coverage when the unit tests are run in the project
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true,
thresholds: {
statements: 80,
lines: 80,
branches: 80,
functions: 80
}
}
- If you want to
create code-coverage reports every time you test
, you can set the following option in the CLI configuration file,angular.json
: This will produce code coverage results whenever tests are run for the project
"test": {
"options": {
"codeCoverage": true
}
}
To find out exact code coverage percentage / To generate a coverage report run the following command in the root of your project:
ng test --codeCoverage
OR- ng test --code-coverage
Generate the code-coverage report at the same time close Karma test window (don't watch unit test cases):
- ng test --no-watch --code-coverage
- ng test --watch=false --code-coverage
Output:
- Angular CLI generates the coverage report in a separate folder called
coverage
at the root - Every folder in the
coverage/src/app
has his index.html, we can load that in the browser to see the actual report for that particular folder or module coverage/index.html
: To check the final report on code coverage of every individual component or page/files or folderscoverage/src/index.html
: Shows report ofpolyfills.ts
andtest.ts
Image - code coverage - folder structure
Image - code coverage - folder structure - vs code
If you load the coverage/index.html
from this folder in the browser, we can see the full report:
Image - code coverage - index - full report
Image - code coverage - individual component
Image - code coverage - Red Gren Highlight - Whats covered
Note:
- To verify code coverage report percentage, one can
comment-uncomment
some code from.spec
files and view the report - In code coverage report,
lines marked in GREEN are covered in test
and lineshighlighted in RED are not covered
in the test (no test written for such lines)
In a simple isolated unit test, we can create a new component as new ComponentName
. But in case of integration test, we need to ask Angular to create an instance of the component with TestBed
utility. TestBed
class provides various utility methods to deal with test cases.
Syntax & Example: Basic setup to write integration tests -
name.component.spec.ts
import { TestBed, ComponentFixture } from '@angular/core/testing';
describe('test-suite-group-name', () => {
let component: ComponentName;
let fixture: ComponentFixture<ComponentName>;
beforeEach( () => {
// create a dynamic module and put component inside dynamic module
TestBed.configureTestingModule({
imports: [ Dependency ModuleName, ],
providers: [ Dependency ServicesName ],
declarations: [ Current ComponentName, Dependency ComponentName ]
});
fixture = TestBed.createComponent(ComponentName);
component = fixture.componentInstance;
fixture.detectChanges();
})
})
Creating an integration .spec test file every time manually is pretty time consuming also there are chances of an error. It is advisable to use Angular CLI to generate components/services/directives/routings, etc.
as Angular CLI generates basic setup code respective to component/test/.spec files.
Angular CLI command to generate component:
ng generate component component-name
OR- ng g c component-name
(which generates 4 files: .ts, .html, .css, .spec file
with some basic setup/scaffolding code)
Syntax & Example: Default code in
app.component.spec.ts
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
// expect(app.title).toEqual('app');
expect(app.title).toEqual('angular-unit-test-demo!');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-unit-test-demo!');
}));
});
TestBed
is theprimary API for writing unit tests
for Angular applications and librariesTestBed
is the basic building block of the angular testing module also the main utility available for Angular-specific testingTestBed
class provides various utility methods to deal with test casesConfigures and initializes environment for unit testing
and provides methods for creating components and services in unit tests- The Angular Test Bed (ATB) is a
higher level Angular Only testing framework
that allows us to easily test behaviors that depend on the Angular Framework - Angular TestBed provides slightly easier way/methods/utilities to create components, handle injection, test asynchronous behavior and interact with our application
- With the help of various utilities of as mentioned above, it
makes the writing test cases with Jasmine easier
TestBed.configureTestingModule()
this is where we configure the spec file takes a similar configuration and emulates as@NgModule
. So, It creates a dynamic testing module and always put this configuration in thebeforeEach()
which runs before every test method- Test suite's/test file's
beforeEach()
block consists ofTestBed.configureTestingModule()
which give it an object with similar values as a regularNgModule
fordeclarations
,providers
andimports
. We can then chain a call tocompileComponents
to tell Angular to compile the declared components - We can create a component fixture with
TestBed.createComponent()
, Fixtures have access to adebugElement
, which will give you access to the internals of the component fixture
We need to use the Angular Test Bed process to perform unit testing of the following things:
- Test Bed allows us to test the interaction of a component or directive with its templates
- debugElement, nativeElement, triggerEventHandler, classes
- It also allows us to easily test change the detection mechanism of the Angular
- fixture.detectChanges();
- It also allows us to test the Dependency Injection process
providers: [ Dependency ServicesName ],
- It allows us to run tests using NgModule configuration, which we use in our application:
TestBed.configureTestingModule({
imports: [ Dependency ModuleName, ],
providers: [ Dependency ServicesName ],
declarations: [ Current ComponentName, Dependency ComponentName ]
});
- It allows us to test user interactions including clicks and input field operation
- triggerEventHandler
We 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
Component fixture class is a wrapper around component with this we can:
- get an instance of the component,
- get DOM elements via nativeElement or debugElement properties,
- we can also run change detection manually, and
- we can also get one or more injected dependencies in this component
Note: What to test?
- All types of bindings:
property binding, class binding, style binding, event binding
- Property binding:
{{ totalCount }}
or interpolation or some value-class property binding render accurately - Class binding: some classes like
[class.highlighted]
conditionally applied correctly - Component object:
Component object initialized
/created properly - Event binding: Methods like
(click)="upCount()"
invoked flawlessly
Syntax & Example:
02-integration-test/01-property-binding/counter-property-binding.component.html
<div class="counter-container">
<span class="icon-menu-up count-button" [class.highlighted]="myCount == 1" (click)="upCount()">
Up Count
</span>
<!-- <span class="totalCountText">Total: {{ totalVotes }}</span> -->
Total:
<span class="totalCountText">{{ totalCounts }}</span>
<span class="icon-menu-down count-button" [class.highlighted]="myCount == -1" (click)="downCount()">
Down Count
</span>
</div>
Syntax & Example:
02-integration-test/01-property-binding/counter-property-binding.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter-property-binding',
templateUrl: './counter-property-binding.component.html',
styleUrls: ['./counter-property-binding.component.css']
})
export class CounterPropertyBindingComponent implements OnInit {
constructor() { }
ngOnInit() {
}
@Input() othersCount = 0;
@Input() myCount = 0;
@Output() count = new EventEmitter();
upCount() {
if (this.myCount == 1)
return;
this.myCount++;
this.count.emit({ myCount: this.myCount });
}
downCount() {
if (this.myCount == -1)
return;
this.myCount--;
this.count.emit({ myCount: this.myCount });
}
get totalCounts(): number {
return this.othersCount + this.myCount;
}
}
Syntax & Example:
02-integration-test/01-property-binding/counter-property-binding.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from "@angular/platform-browser";
import { CounterPropertyBindingComponent } from './counter-property-binding.component';
describe('CounterPropertyBindingComponent', () => {
let component: CounterPropertyBindingComponent;
let fixture: ComponentFixture<CounterPropertyBindingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CounterPropertyBindingComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CounterPropertyBindingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create CounterPropertyBindingComponent', () => {
expect(component).toBeTruthy();
});
/* binding property - interpolation */
it('should bind-show-render totalCount', () => {
component.othersCount = 10
component.myCount = 5;
fixture.detectChanges();
let totalCountTextElement = fixture.debugElement.queryAll(By.css('span'));
let totalCountTextNativeElement: HTMLElement = totalCountTextElement[1].nativeElement;
expect(totalCountTextNativeElement.getAttribute('class')).toBe('totalCountText');
// expect(totalCountTextNativeElement.innerText).toContain(15);
});
/* binding style/class */
it('should highlight the upCount button if totalCounts is 1', () => {
component.myCount = 1;
fixture.detectChanges();
let upCountButtonElement = fixture.debugElement.query(By.css('.icon-menu-up.count-button'));
expect(upCountButtonElement.classes['highlighted']).toBeTruthy();
});
/* binding event */
it('should increase totalCounts by 1 when upCount button clicked', () => {
let upCountButtonElement = fixture.debugElement.query(By.css('.icon-menu-up.count-button'));
upCountButtonElement.triggerEventHandler('click', null);
// component.upCount();
expect(component.totalCounts).toEqual(1);
});
});
Syntax & Example:
event-binding.component.spec.ts
/* binding event */
it('should increase totalCounts by 1 when upCount button clicked', () => {
let upCountButtonElement = fixture.debugElement.query(By.css('.icon-menu-up.count-button'));
upCountButtonElement.triggerEventHandler('click', null);
// component.upCount();
expect(component.totalCounts).toEqual(1);
});
- In Angular application whenever we make any changes in DOM element, Angular runs its
change Detection
algorithm automatically - Note: In a testing environment, Angular does not run the
change Detection
algorithm automatically so we need to explicitly/manually call it by usingfixture.detectChanges();
- In a testing environment, Angular doesn't automatically bind the component's properties with the template elements.
- You have to explicitly call
fixture.detectChanges()
every time you want tobind a component property with the template
fixture.detectChanges()
is called toupdate the DOM with the new values
- You have to explicitly call
- We use
fixture.detectChanges
to instruct Angular torun change detection
before doing our assertions with Jasmine’sexpect
- By using the
ATB = Angular TestBed
andfixtures
we can inspect the components view throughfixture.debugElement
and also trigger a change detection run by callingfixture.detectChanges()
- When we create a component on the TestBed, Angular does not automatically initiate change detection which means that our
template will not be rendered with its bindings evaluated
- This simply means that
we need to manually trigger change detection
thankfully our handycomponent fixture
makes this easy - Whenever we want to trigger change detection, we just need to call
fixture.detectChanges
either at the bottom of ourbeforeEach
block or in every test cases
- This simply means that
Syntax & Example:
fixture.detectChanges()
it('should highlight the upCount button if totalCounts is 1', () => {
component.myCount = 1;
fixture.detectChanges();
let upCountButtonElement = fixture.debugElement.query(By.css('count-button'));
expect(upCountButtonElement.classes['highlighted']).toBeTruthy();
});
it('should have a title Welcome to Angular Testing', () => {
component.title = 'Welcome to Angular Testing';
fixture.detectChanges();
expect(element.textContent).toContain(component.title);
})
- In case of Unit test, we use to provide dependencies used in constructor as
loginFormComponent = new LoginFormComponent(new FormBuilder);
- But for the Integration test, we have to use a different approach as
providers
, the same as we used in module.ts/class files - Any external services used at Component level must be added under .spec/test
providers
(in some cases we need to mention Module level dependencies/services also like HTTP or HttpModule)
Syntax & Example:
Providers in Integration test:
import { Dependency ServicesName } from './path';
TestBed.configureTestingModule({
imports: [ Dependency ModuleName, HttpModule ],
providers: [ Dependency ServicesName, DataService ],
declarations: [ Current ComponentName, Dependency ComponentName, EmployeeComponents ]
});
Note: If dependencies are not provided properly than we get
Error: No provider for service!
. In the case of component, dependencies can be added at either Module level or Component level.
- In Angular application,
ngOnInit
method is called by Angular automatically at the time of component initialization - If
implements OnInit
orexport class ComponentName implements OnInit
statement is used than onlyngOnInit()
method executes automatically elsengOnInit()
is normal method present in class - When performing testing we need to call
component lifecycle hooks ourselves
, likengOnInit()
by ourself ascomponent.ngOnInit()
, Angular won’t do this for us in the test environment
If Module level dependencies used in module.ts
as: providers: []
than write test case as it('should load todos from the server', () => { let loginService = TestBed.get(LoginService)})
Syntax & Example:
module level service dependencies inserted under providers arrays
@NgModule({
imports: [
CommonModule
],
providers: [
LoginService,
AuthenticationService,
]
declarations: [ LoginComponent ]
})
To get dependencies used at component level write test case as it('should load todos from the component', () => { let loginService = fixture.debugElement.injector.get(LoginService)})
@Component({
selector: 'login-component',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
providers: [LoginService, AuthenticationService]
})
Defines a schema that allows any property on any element, so Angular Test ignores any custom elements, attributes in HTML file while testing.
Let’s run the npm test and we would see a bunch of errors because of nesting components. since the app component has other components inside such as header, footer, and add-item.component, etc.. We have to declare those in the app.component.spec.ts file TestBed.configureTestingModule()
blocks declarations
array
Another way is to use NO_ERRORS_SCHEMA
which tell angular “Angular, please ignore all the unrecognized tags while testing app.component”
Syntax & Example:
schemas: [ NO_ERRORS_SCHEMA ]
import { NO_ERRORS_SCHEMA } from '@angular/core';
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ComponentName ],
schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents();
}));
NO_ERRORS_SCHEMA
simply telling the TestBed to ignore any template errors.
So, while testing when it hits <header-component>
tag, you don’t have to import HeaderComponent
into your TestBed in your spec file. Angular will just treat that tag like any old div and continue on its way. In the above example, you don’t need to import any of the child components as well as custom components of ParentComponent.
However, there’s a big gotcha! and thing to understand for everyone is that: it also will ignore unknown attributes; both property bindings and event handlers
. Here is an example:
<footer [checking]="testing" (customClicked)="onClick($event)"> </footer>
Now let us discuss the problem - The bindings in FooterComponent are to [checking]
and (customClicked) not click
. But the unit tests will still compile! They’ll even run!
.
ng build --prod
will catch the property binding problem, [checking]
, but will not catch the misnamed (customClicked) handler. The only way to find that one is in integration testing! so overpowering NO_ERRORS_SCHEMA
could be causing damage.