Beginner’s Guide to Ionic Angular Unit Testing (Part 1)
Angular is one of our favorite frameworks to work with — it might be complex to learn at first, but — it is a complete framework for front-end web development. As the framework is maturing by each version — it has become a reliable choice of any Long Term maintenance project.
However, one can’t rely on software unless there is an assurance of code running properly every time you use it (or change it). That is why there is Testing — which automates the work of checking each aspect of code after every change. There are various ways of testing the code in Angular also -
- Unit Testing
- Integration Testing
- End-to-End testing
In this series — we will cover techniques for unit testing Angular applications using the Ionic 5 Framework — which a popular mobile development framework for hybrid apps. You can use the same concepts in other Angular based apps also — not necessarily be Ionic. Testing your Angular application helps you check that your app is working as you expect.
Let’s see the testing principles and understand the difference between various testing approaches. Before writing tests for your app, you should have a basic understanding of the following concepts:
Unit Testing — It deals which checking only the JavaScript( or Typescript) part of the code. It is called Unit as we check only one unit of code specification at a time it can be a single condition or some functions output. However, this testing doesn’t tell us about whether the whole system works fine or not. Even it can’t tell you anything outside the scope of the unit — which is under test.
Integration Testing — This is not always a common term in testing but in the case of Angular, it means working with the HTML templates along with TypeScript. So, if we want to check what rendered on the template — we will need Integration testing. As the Unit test can only tell you what is stored in the variables — it can’t tell you what happened in the browser and what was rendered. We will also be covering this topic in this series.
End-to-End testing — This is a complete flow testing of a feature. Suppose you have a feature — add the item to the Todo list. It will check the whole flow — right from adding to the object to it being added to the database and displayed back in the list. It is more complex to write such tests and they break easily once anything in the whole process changes. They are good for some sanity checks of the system — but writing e2e tests for everything in your system can’t be justified. The effort in testing might be more than developing the feature. We are not covering this kind of testing in our series.
Understanding the concept of Unit Testing 💡
Unit testing as we described tests only a single point in the code e.g. a value of a variable. It may also check the return value of a function. But the main concept is — it only tests 1 major expectation after some activity happens.
Now, for a developer like me who has never been into testing. It was hard to understand how testing works for unit testing in ionic applications. Whether the real code runs, does it runs completely like a full app, or just function we call is executed. We will clarify many such doubts in this series. In reality — it is a mix — where some part is real code and some part is fake code (fake data or functions). Unit tests and automated tests, in general, can help to verify that your codebase is behaving correctly.
Our main focus in the UNIT test remains that only — the system under test should have the real code running. Whatever outside scope of this function may be real or fake — it doesn’t matter. For example. If you are testing a Boolean variable’s value it doesn’t matter whether that value resulted in some action (like a popup or something else). Only what matters it that is the value of the Boolean variable. However, in some other test cases, you might be more interested in whether Popup was called or not — Hence your expectations are everything in Unit testing. 🎅
Let us see how to unit test an Ionic/Angular Application.
Setting up the Environment for Ionic Angular Testing
The good part in Angular or Ionic Angular — we are getting everything we need for testing your Ionic App already set up. That is a Server to run the tests (Karma), a language to write our specifications (Jasmine), and other TestBed configuration which are seemingly complicated — at first.
Ionic Unit Testing Example
Step 1 :
I have chosen to use the tabbed version of the Ionic template to start with, if you want to go along, you can use the following command to install the tabs version of Ionic Angular Template :
ionic start ionic-testing tabs --type=angular --capacitor
Step 2 :
After running the whole setup you can check out if everything is working — by running the following command in your project directory.
ionic serve
You must get a screen like the following — (I am in dark mode — so don’t worry if yours is a little different) working fine ✌️
Step 3: Set up testing
After checking the setup, we can check whether the test environment is working fine or not. We can use the command:
npm test
This will run the ng test CLI command as written in your package.json.
The log will show that Karma ran three tests that all passed.It will be shown in the last line of the log. A Chrome browser opens and displays the test output in the “Jasmine HTML Reporter” like this.
Most people find the browser output(shown above) easier to read than the console log. You can click on a test row to re-run just that test or click on a description to re-run the tests in the selected test group (“test suite”). Meanwhile, the ng test command is watching for changes.
However, they are not very meaningful but they tell that our TestBed is working properly. If you run this command on an existing code this may not run properly because you might have many dependencies in existing code which are not available to testBed (more on this later).
Now before writing our first test case, let’s jump into some basics about the setup and configuration of Angular Testing.
We will be using Karma and Jasmine to run the unit tests in our application.
Karma Server
As defined on the Karma runner official site. (http://karma-runner.github.io/5.0/intro/how-it-works.html)
Karma is essentially a tool which spawns a web server that executes source code against test code for each of the browsers connected. The results of each test against each browser are examined and displayed via the command line to the developer such that they can see which browsers and tests passed or failed.
A very complicated explanation — for us it simply means Karma starts a web server that runs our source code and also test code — and checks the results. It can work with multiple browsers in parallel — but in our case, we will stick only to 1 browser instance.
In our case, it will open a new Chrome instance — why chrome? It is set as default in the karma.conf.js .
This may be different for you. Right now we don’t want to change anything in this file — but you can always come back to this file in future to take more control on your Karma 😎
Jasmine
As given on official Jasmine website :
“Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.”
Forget about BDD — if you are not knowing what it is. Other parts of the definition must be very clear to you.
The CLI takes care of Jasmine and Karma configuration for you but you can search the web for more details about Jasmine and Karma configuration. We will discuss some of the jasmine basics & Angular Testbed — after we run our first test.
Understanding Spec file
The tests are always written in SPEC files (.spec.ts). These will be automatically created if you use CLI commands to generate Angular components.
I am using tab1.page.spec.ts which is the spec file for Tab1 Ionic Page. Here is how it looks like.
If you check the spec file there is only one test case
it(‘should create’, () => { expect(component).toBeTruthy(); });
It just checks whether the Angular component is created and hence it passes if it is truthy. Placing your spec file next to the file it tests and placing your spec files in a test folder are the two conventions to be adopted in your own projects for every kind of test file.
Let’s quickly jump off some of the special names used in the spec file to understand it more. We have 2 sets of jargon: Angular based and Jasmine based.
Angular things :
TestBed.configureTestingModule({ declarations: [Tab1Page], imports: [IonicModule.forRoot(), ExploreContainerComponentModule] }).compileComponents();
fixture = TestBed.createComponent(Tab1Page); component = fixture.componentInstance; fixture.detectChanges();
TestBed: It is something like an Angular module which contains all dependencies required for testing the particular component. You can already see its similarity to ngModule with declarations and imports there. You can also learn more about Testing Ionic Applications with TestBed if you are using Angular to build your Ionic applications.
Fixture: it is a wrapper that can access the HTML and TS components also.
We use a fixture to create components and services for instance.
In this example, we just create a component for Tab1Page. Pages are Angular components.
fixture.detectChanges() is a line which start angular change detection Lifecycle. It can also initiate ngOnInit call as that runs on the loading of DOM.
Jasmine things :
describe('Tab1Page', () => { // Test Suite
beforeEach(async(() => { // Setup //Angular TestBed Part
}));
it('should create', () => { // Test Case expect(component).toBeTruthy(); });
});
describe — This is the top-level wrapper and creates something called a test suite — a test suite can have multiple tests in it. Other describe
calls are nested within it which define major areas of functionality. The spec
files have multiple describe
calls nested inside a single describe
call for hierarchical breakdown of functionality.
it — the it
in the file is called 1 spec — i.e. one test in the suite. It has a complete description in English about the test we are running and its expectations part in the function. It defines individual test cases.
expect — this keyword is used for Jasmine expectation matching. Similar to the logical operator is programming it checks of matching things and returns true or false. ( test failed or passed)
beforeEach — this part of code is to avoid code duplication and put all lines which are common for each test in a single block. So this section runs before every spec (it) part. There can multiple beforeEach defined in a TestSuite( describe)
afterEach — this the part which runs after each test. (Not used in current code)
beforeEach and afterEach are popularly knowns as setup and teardown code part of testing.
So now finally we can create and run unit test. Let us write our actual tests 😅
Before writing the test, for unit testing Ionic applications, I will add some functionality of our component.
I added a simple function called addJob
to the component which adds a job
string to an array jobs
.
We can test 2 things here :
1.jobs
should be an array, we can check that by checking if length
property is defined. You should not use the truthy
check as it will take 0
length as a falsy
value.
it('jobs should be an array', () => { expect(component.jobs.length).toBeDefined(); });
2. addJob
should add the string to the jobs
array. We can confirm this by checking if length
of the array is greater than 0 (not a perfect test). Also by checking whether the element added now exists in thejobs
array.
it('addJob should add the job string to jobs array', () => { const job = 'Dummy Job';
component.addJob(job); // action part
expect(component.jobs.length).toBeGreaterThan(0); expect(component.jobs).toContain(job);
});
In the above test, we have an action part, where we call actual addJob. All setup parts or variable declarations should be before action part and all expect should be after this part. Otherwise, you will not get the desired result.
Here is our result — all Tests passing. Look at the 3 tests in Tab1Page.
Using the F word 🤐 : Only run specific Test cases
To run only one or some of the specific specs you can put f
in front of them so it
will become fit
and describe
will become fdescribe
. So depending on what you did on these tests OR test suites will run. Other things will be ignored. It is very useful if you have a lot of tests and you don’t want to run everything — especially during creating new tests.
Using the X: Ignore tests or make them Pending
To disable some test cases you can use x
in front of them this will make them xit
— and they will be ignored/pending. Other specs will run as before. This is useful if you want to ignore some test cases for some reason.
So this is just the beginning of our testing journey. In the next part of this series, we explore more useful testing techniques called Mocks and Spies 🕵️