Skip to content

Test implementation guidelines for Ignite UI for Angular

Plamena Miteva edited this page Apr 7, 2020 · 35 revisions
Version User Date Notes
0.1 Zdravko Kolev June 10, 2019 Initial version
0.2 Nikolay Alipiev January 17, 2020 Angular component unit testing
0.3 Nikolay Alipiev / Plamena Miteva March 30, 2020 Define new Guidelines
  • Radoslav Karaivanov | Date:
  • Konstantin Dinev | Date:

Prerequisites

Examples

Here are some good examples of how to write new tests and how some of the old ones are refactored using the guide below.

New tests

Refactored tests

Test implementation guidelines

Note: those guidelines should be applied only if reasonable

Globals

  1. Define constants, when using a class name as a selector, at the top of the tests:
const CSS_CLASS_DRAG_ROW = 'igx-grid__tr--drag';
  1. Define types for all variables and parameters:
let grid: IgxGridComponent;
  1. The helper functions for the tests are in HelperUtils. Review and reuse them in tests instead of rewriting them.

  2. For better performance, use NoopAnimationsModule to disable animatoions in tests where they are not the subject of tests:

import { NoopAnimationsModule } from '@angular/platform-browser/animations';
  1. Implement different tests for each scenario, based on the Test plan.

Describes & TestBeds

  1. Describes should be organized according to features/functionalities
  2. Create one TestBed for each describe using beforeAll
  3. In the TestBed declarations put only components which are used in the tests bellow and import necessary dependencies
describe('Initialization and rendering tests: ', () => {
    configureTestSuite();
    beforeAll(async(() => {
        TestBed.configureTestingModule({
            declarations: [
                IgxComboSampleComponent
            ],
            imports: [
                IgxComboModule,
                NoopAnimationsModule,
                IgxToggleModule
            ]
        }).compileComponents();
    }));
    ...
  1. Call the configureTestSuite() method before the TestBed setup.
describe('Initialization and rendering tests: ', () => {
    configureTestSuite();
  1. Use beforeEach in cases when all the tests use only one TestBed component rather than duplicate the TestBed initialization Example:
describe('IgxGrid - Multi Cell selection', () => {
    configureTestSuite();
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [
                SelectionWithScrollsComponent,
                SelectionWithTransactionsComponent
            ],
            imports: [NoopAnimationsModule, IgxGridModule]
        }).compileComponents();
    }));
    describe('Base', () => {
        ...
        beforeEach(fakeAsync(/** height/width setter rAF */() => {
           fixture = TestBed.createComponent(TestComponentX);
           ...
        }));

        it('Should be able to select a range with mouse dragging', () => {
...
  1. When adding/creating tests for the grids (igxGrid, igxTreeGrid, igxHieraticalGrid) the TestBeds from the files grid-base-components.spec, grid-samples.spec and tree-grid-components.spec.ts should be used or extended /Most probably one of the test components already defined in them would be suitable for your scenario/. When a new test component is necessary, add it to one of these files so it can be easily reused later.

  2. Variables used in all/many tests is better to be defined in the scope of the describe and then reassigned when needed rather than repeat their declaration in each test:

describe('Custom ghost template tests', () => {
     let grid: IgxGridComponent;
     let rows: IgxGridRowComponent[];
     let dragRows: DebugElement[];
     configureTestSuite();
     beforeAll(async(() => {
     ...

Tests

  1. One of the techniques for unit testing is to create a component/service/directive and inject its dependencies by hand while calling its constructor as described in Angular tutorials. Here is an example of such an approach:
select = new IgxSelectComponent(null, mockCdr, mockSelection, null, mockInjector);
  1. There are several approaches to create a dependency:
  • calling its constructor
const selectionService = new IgxSelectionAPIService();
combo = new IgxComboComponent({ nativeElement: null }, mockCdr, selectionService, mockComboService, null, mockInjector);

However, creating the real dependency might be a rather difficult task as it may depend on several other dependencies which also have to be created and injected.

  • mock the dependency
  • use a dummy value
const dropdownContainer = { nativeElement: { focus: () => {}}};
combo['dropdownContainer'] = dropdownContainer;
  • create a spy
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']);
combo.dropdown = dropdown;
  1. Tests should be excluded with pending() function; Do not use x. Example:
it('my it block', () => {
   pending('my pending comment');
   ...
});
  1. Don't use fix.WhenStable(), done(), setTimeout() when possible. if possible. Use (), then if needed fakeAsync() and at the end async(). Where there is async operations try using fakeAsync() with tick(), or if needed async() with await wait(). Sync tests should not have tick(), for async try the following order: tick() -> tick(DebounceTime ) -> await() -> await(DebounceTime) (TODO examples)
  2. Where possible call components' API methods instead of making UI interaction.
  3. For Virtualization use igxFor scroll methods (scrollTo()). You need async() for them.
  4. Consider Jasmine async and callback when using promises - whenStable, rendering done, etc.
  5. When using await wait use it with fixture.detectChanges(). When there are delays in the execution. For example, when testing Grid's scrolling or Resizing - this is because the scrolling is happening out of the zone.
  6. Create helper functions - remove duplication and eventually move it to util.js (or use logic from there, especially keyboard triggers). There is already a lot of code in util.
  7. Events - use triggerEventHandler() for the real interaction, instead of dispatchEvent(). In that case Angular lifecycle is skipped. Use util.
  8. Focus events - use element.triggerEventhendle('focus') instead of element.nativeElement.focus().
  9. Use Spies to:
  • check for different calls. Example:
    it('should not trigger onRemove event when ..', () => {
        ...
        const firstChipComp = fix.componentInstance.chips.toArray()[0];
        spyOn(firstChipComp.onRemove, 'emit');
  • prevent a certain function from execution. For example:
    // Spy the saveBlobToFile method so the files are not really created
    spyOn(ExportUtilities as any, 'saveBlobToFile');
  1. When keyboard or mouse events are needed, the functions from the UIInteractions (ui-interactions.spec) should be used. For example UIInteractions.triggerKeyDownEvtUponElem('tab', cell.nativeElement, true),

Refactor

In the end always do a self-review, to fix unclear test names, syntax errors, etc.

Future improvements

  • Move e2e test outside unit test.
  • Avoid defining test components in the test files. Define them in a separate file instead (for example, grid-samples.spec.ts) so they can be easily reused in other test files.
Clone this wiki locally