-
Notifications
You must be signed in to change notification settings - Fork 160
Test implementation guidelines for Ignite UI for Angular
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:
- Angular Testing Guide
- Jasmine
- Run tests locally with
ng test igniteui-angular
Note: those guidelines should be applied only if reasonable
- 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';
- Define types for all variables and parameters:
let grid: IgxGridComponent;
- 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';
- Describes should be organized according to features
- Create one testbed for each describe, e.g.
IgxComboSampleComponent
- Testbed component to be also declared for the specific describe
describe('Initialization and rendering tests: ', () => { configureTestSuite(); beforeAll(async(() => { TestBed.configureTestingModule({ declarations: [ IgxComboSampleComponent ], imports: [ IgxComboModule, NoopAnimationsModule, IgxToggleModule, ReactiveFormsModule, FormsModule ] }).compileComponents(); })); ...
- When writing tests for a new control/functionality the method
configureTestSuite()
must be called.
describe('Initialization and rendering tests: ', () => {
configureTestSuite();
- Define variables, acting for all tests, in the scope of the describe:
describe('Custom ghost template tests', () => { let grid: IgxGridComponent; let rows: IgxGridRowComponent[]; let dragRows: DebugElement[]; configureTestSuite(); beforeAll(async(() => { ...
- Define types for all variables that you define.
- Following Angular tutorials for component unit testing, what they suggest is to directly instantiate the component instead of creating a test fixture. Here is an example of such an approach:
select = new IgxSelectComponent(null, mockCdr, mockSelection, null, mockInjector);
Note: for such cases, you will need to mock the everything that you don't need including component features. If there are exceptions, in most cases, this will mean that a part of the component is not being mocked. Later move mock methods to util file. The testBed.get(), debugElement.injector() - instead of mocking, if mocking is complicated - get Directive
input.injector.get(igxDateEditorDirective)
. Use methods, prop, (check row drag), mock methods, but use public properties from object prototype???
const mockInjector = jasmine.createSpyObj('Injector', {
'get': mockNgControl
});
Note: In addition to all the unit tests, there is a single test that is responsible for testing if the bandings are properly called. Once this is tested, all other related tests can be unit tests using API.
Note: If there are several unit tests with direct component initialization, then you can specify separate describe for unit tests, but not necessary.
- Tests should be excluded with pending() function; Do not use
x
. Example:
it('my it block', () => {
pending('my pending comment');
...
});
- Don't use fixWhenStable(), done(), setTimeout 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)
- Where possible call components' API methods instead of making UI interaction.
- For Virtualization use igxFor scroll methods (scrollTo()). You need async() for them.
- 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.
- Events - use triggerEventHandler() for the real interaction, instead of dispatchEvent(). In that case Angular lifecycle is skipped. Use util.
- ??? Focus events -> focus() and blur() should not be tested
- Use separate
describes
. When multiple tests are using the sametestBed
, it should be initialized in abeforeEach
. 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', () => {
...
-
The helper functions for the tests are in HelperUtils. Review and reuse them in tests instead of rewriting them. For example,
verifyCheckbox()
,clearOverlay()
etc. -
When adding tests for the Grids (
igxGrid
,igxTreeGrid
,igxHieraticalGrid
) the test beds from the filesgrid-base-components.spec
,grid-samples.spec
andtree-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. -
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. -
Implement different tests for each scenario, based on the
Test plan
. -
Common logic should be exposed in separate functions. Example:
// tests column and column group header rendering
function testColumnGroupHeaderRendering(column: DebugElement, width: number, height: number,
title: string, descendentColumnCssClass?: string, descendentColumnCount?: number) {}
-
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)
,UIInteractions.simulateClickAndSelectCellEvent()
,UIInteractions. simulateTouchMoveEvent()
etc. New event functions should also be added here. -
Use
fakeAsync
when there are async operations (animations, rAF, timeouts). Most grid testbeds setups need to befakeAsync
(orasync
). -
Use
native
async (whenfakeAsync
is not an option) - Example.
- When there are delays in the execution. For example, when testing
Grid's scrolling
orResizing
- this is because thescrolling
is happening out of thezone
. - When using
await wait
withfixture.detectChanges()
.
- Consider Jasmine
async
andcallback
when using promises - whenStable, rendering done, etc. - Avoid using
done()
callback, withtimeout()
andfixture.whenStable
when possible. - 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');