Testing Ionic Applications
1. Setting Up Testing Environment
Example: Install testing dependencies
npm install --save-dev @ionic/angular-test @testing-library/angular jest jest-environment-jsdom
| Dependency | Description | Version |
|---|---|---|
| @ionic/angular-test | Official testing utilities for Ionic Angular apps | NEW 7.2.0 |
| @testing-library/angular | Angular testing utilities for DOM interactions | 15.0.0 |
| jest | JavaScript testing framework | 29.5.0 |
| jest-environment-jsdom | JS DOM environment for Jest tests | 29.5.0 |
Note: Ensure your
tsconfig.spec.json includes the necessary test configurations.
Example: Configure Jest for Ionic
// jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom',
setupFilesAfterEnv: ['<rootDir>/src/test.ts'],
moduleNameMapper: {
'^src/(.*)
2. Writing Unit Tests
Example: Basic Unit Test for Ionic Component
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { MyComponent } from './my-component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [IonicModule.forRoot()]
}).compileComponents();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
| Test Type | Description | Framework | Notes |
|---|---|---|---|
| Unit Test | Tests individual components or functions in isolation | Jest, Jasmine | Use Angular TestBed for Ionic components |
| Integration Test | Tests interaction between components | Jest, Jasmine | Use TestBed with multiple components |
| End-to-End Test | Tests full application flow in a real browser | Cypress, Protractor | Simulates user interactions |
Note: Ionic components often require
IonicModule.forRoot() in test configurations to render correctly.
Warning: Avoid testing DOM manipulation directly; use Angular's testing utilities instead.
3. Testing Components
Example: Testing a Button Component
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { MyButtonComponent } from './my-button.component';
describe('MyButtonComponent', () => {
let component: MyButtonComponent;
let fixture: ComponentFixture<MyButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MyButtonComponent],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(MyButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should emit click event', () => {
spyOn(component.clickEvent, 'emit');
component.onClick();
expect(component.clickEvent.emit).toHaveBeenCalled();
});
});
| Property | Description | Default Value | Notes |
|---|---|---|---|
| text | Text content of the button | '' |
Required |
| type | Button type (button, submit, reset) | 'button' |
Valid values: button, submit, reset |
| disabled | Disables the button if true | false |
Boolean flag |
Note: Use
TestBed to configure the testing module and create component instances.
Warning: Ensure all dependencies are properly mocked or provided in the test module.
Testing Component Workflow
- Import necessary testing modules and components
- Configure the test module with
TestBed - Create component instance using
TestBed.createComponent() - Run tests using Jasmine or Jest syntax
4. Mocking Services
| Service | Mocking Approach | Notes |
|---|---|---|
| HttpService | HttpClientTestingModule |
Use Angular's testing module for HTTP requests |
| StorageService | Storage mock with jest.fn() |
Mock storage methods like get, set |
| UserService | Manual mock with spyOn |
Mock user data and auth logic |
Example: Mocking HttpService
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
describe('MyComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [MyService]
});
});
it('should fetch data', () => {
const service = TestBed.inject(MyService);
const httpSpy = spyOn(service.http, 'get').and.returnValue(of({ data: 'mocked' }));
service.fetchData().subscribe(res => {
expect(res).toEqual({ data: 'mocked' });
});
});
});
Example: Mocking StorageService
import { Storage } from '@ionic/storage-angular';
const mockStorage = {
get: jest.fn(),
set: jest.fn()
};
describe('MyComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: Storage, useValue: mockStorage }
]
});
});
it('should use mocked storage', () => {
const service = TestBed.inject(MyService);
mockStorage.get.mockResolvedValue('mockedValue');
service.getData().then(res => {
expect(res).toBe('mockedValue');
});
});
});
5. Testing Navigation
Example: Testing Navigation with Ionic
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
describe('MyApp', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyApp],
imports: [
IonicModule.forRoot(MyApp),
RouterTestingModule
]
});
});
it('should navigate to home page', () => {
const fixture = TestBed.createComponent(MyApp);
const app = fixture.debugElement.componentInstance;
app.nav.push(HomePage);
expect(app.nav.getActive().component).toBe(HomePage);
});
});
| Method | Description | Parameters | Return Type |
|---|---|---|---|
| nav.push() | Navigates to a new page | component, params |
Promise<any> |
| nav.pop() | Pops the current page off the stack | options |
Promise<any> |
| nav.setRoot() | Sets the root page of the app | component, params |
Promise<any> |
| nav.getStack() | Gets the navigation stack | none |
Array<any> |
Note: Use
RouterTestingModule to mock navigation in unit tests.
Warning: Ensure that navigation logic is tested in isolation to avoid side effects.
6. Writing E2E Tests
E2E (End-to-End) tests in Ionic applications ensure that the app behaves as expected from user interaction to backend responses. These tests are typically written using tools like Playwright or Cypress.
| Test Framework | Features | Notes |
|---|---|---|
| Playwright | supports web, mobile, and desktop apps |
Good for cross-platform testing with rich API |
| Cypress | fast, easy to debug |
Great for web apps and component testing |
Example: Playwright E2E Test Setup
import { test, expect } from '@playwright/test';
test('should display welcome message', async ({ page }) => {
await page.goto('/');
const heading = page.locator('h1');
await expect(heading).toHaveText('Welcome to Ionic!');
});
Example: Cypress E2E Test Setup
describe('Ionic App E2E Tests', () => {
it('should show welcome message', () => {
cy.visit('/');
cy.get('h1').contains('Welcome to Ionic!');
});
});
Note: E2E tests should be run in a CI/CD pipeline to ensure app stability across environments.
7. Testing User Interactions
Example: Simulating Tap Events
import { ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
it('should handle tap on button', () => {
const fixture = TestBed.createComponent(MyComponent);
const button = fixture.debugElement.query(By.css('ion-button'));
button.triggerEventHandler('click', null);
fixture.detectChanges();
expect(component.buttonClicked).toBeTrue();
});
| Event | Description | Testing Method |
|---|---|---|
| tap | Simulates a tap interaction on an element | triggerEventHandler('click', null) |
| swipe | Simulates swipe gesture on a component | Use UIEvent or mock touch events |
Note: For mobile-specific interactions, consider using
ion-content and ion-scroll event simulations.
Example: Testing Scroll Events
it('should handle scroll event', () => {
const fixture = TestBed.createComponent(MyScrollComponent);
const scrollElement = fixture.debugElement.query(By.css('.scroll-container'));
const scrollEvent = new UIEvent('scroll');
scrollElement.triggerEventHandler('scroll', scrollEvent);
fixture.detectChanges();
expect(component.scrollPosition).toBe(100);
});
8. Implementing Visual Regression Testing
Example: Setting up Percy for Ionic
// percy.config.js
module.exports = {
snapshot: {
widths: [375, 768, 1024],
minHeight: 1024
},
upload: {
asset: {
ignore: ['**/node_modules/**']
}
}
}
| Feature | Description | Usage |
|---|---|---|
| percy snapshot | Command to capture visual snapshots | percy snapshot src/**/* |
| percy exec | Wrapper to run tests with Percy | percy exec npm test |
Note: Percy requires a Percy token for uploading snapshots to the cloud.
Visual Regression Tools Comparison
- Percy: Cloud-based, easy to integrate with CI
- Selenium: Self-hosted, more control over browser behavior
- Playwright: Modern, fast, built-in screenshot support
Setup Workflow
- Install Percy CLI:
npm install -g @percy/cli - Set up Percy project in dashboard
- Add Percy to test runner configuration
- Run tests with visual snapshots
9. Testing Platform-Specific Code
Testing platform-specific code in Ionic applications requires understanding how to handle device-specific APIs and conditional logic. Below are key approaches and considerations.
| Feature | Description | Testing Approach |
|---|---|---|
| Device APIs | Access to native device features like camera, GPS, etc. | Use cordova-plugin-xxx or @capacitor/xxx in tests with mocks or stubs. |
| Platform Detection | Detecting the current platform (iOS, Android, Web) | Use Platform service from @ionic/angular or @capacitor/core. |
| Conditional Logic | Executing code based on platform | Wrap platform-specific logic in conditional blocks and test each branch separately. |
Example: Platform Detection in Tests
import { Platform } from '@ionic/angular';
describe('Platform-specific logic', () => {
let platform: Platform;
beforeEach(() => {
platform = new Platform();
});
it('should run web logic on web platform', () => {
spyOn(platform, 'is').and.returnValue(false);
// Test web-specific logic here
});
it('should run mobile logic on mobile platform', () => {
spyOn(platform, 'is').and.returnValue(true);
// Test mobile-specific logic here
});
});
Note: When testing platform-specific code, ensure that your tests simulate the correct environment (e.g., mobile vs web) to accurately reflect runtime behavior.
10. Running Tests
| Command | Description | Flags |
|---|---|---|
| npm run test | Run all tests in the project | --watch, --coverage |
| npm run test -- --watch | Run tests in watch mode | |
| npm run test -- --coverage | Generate test coverage report |
Note: Ensure Jest is configured in your Ionic project for effective test running.
11. Measuring Code Coverage
| Feature | Description | Command | Notes |
|---|---|---|---|
| istanbul | Code coverage tool for JavaScript/TypeScript | npx istanbul cover _mocha |
Used with Mocha for test coverage |
| nyc | Next generation Istanbul code coverage tool | npx nyc mocha |
Alternative to Istanbul with better performance |
| jest | JavaScript testing framework with coverage support | jest --coverage |
Built-in coverage reporting |
Example: Setting up Jest with Coverage
{
"scripts": {
"test": "jest --coverage"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{ts,js}",
"!src/**/*.d.ts"
]
}
}
Note: Jest generates coverage reports in
: '<rootDir>/src/$1',
},
collectCoverageFrom: [
'src/**/*.{ts}',
'!src/main.ts',
'!src/polyfills.ts'
]
}
coverage/lcov-report/index.html