Unit & Integrated tests #
NOTE : Check the reference doc regarding Testing APIs for Angular .
Angular uses Jasmine for unit testing and Karma to run the tests on browsers. Tests can be:
Isolated #
- Only tests the JS class not the component
- Mostly used for services and pipes
- Need to create instances of classes on your own
Integrated #
- Tests the class and template together
- Mostly used for components and directives
- The objects are created by the framework
Unit tests #
They test a single unit (like a class, function, or a bunch of classes) in isolation. Some features of unit tests:
- Fast (to code and to execute)
- Should handle one state change at max
- Assert one thing
- Minimal dependencies
They follow a structure of:
- Assemble - The setup code for the test
- Action - The thing that the user will likely do. Can be a state change
- Assert - “Is the result what is expected?”
Run unit tests with ng test which asks Karma to look for *.spec.ts files and execute the tests within them.
A basic test looks like this:
describe('a nice test', () => {
// do something
beforeEach(() => {
// code to run before each 'it'
})
it('exact test stuff', () => {
// do something
expected(valA).toBe('good stuff');
})
})
toBe is just one of the many “matchers” jasmine has. The official docs has
a list of matchers
Mocking #
To minimize depedencies on external code they are mocked for things like network calls, DB use, side effects etc. E.g. instead of making HTTP requests with the http dependency use a mock http dependency that simulates network calls and returns results immediately.
- Mock with jasmine like this:
const mockStuff jasmine.createSpyObj('some desc', ['method1', 'method2', 'method3']). - A particular method can be modified to return a particular value like this:
mockStuff.method1.and.returnValue(anyValue) - To check if a method is being called in the function with particular params, use:
expect(mockStuff.method2).toHaveBeenCalledWith(param1, param2)where the parameters can be exact values or it can just check that the types match by passing in an arg likejasmine.any(ClassName) - Components can be tested just like services for isolated tests. Instantiate the class with mock dependencies, set any input params and then test whatever methods needed to be tested.
Integrated tests #
They’re more complex and are meant to test an entire functionality. It can test the template and the component in an environment that’s a close approximation to real world usage.
Setup #
This is more complex since Angular creates the instances of the component and template and places them in a module. This setup goes in a beforeEach. The code for this is usually generated by the cli when using ng g c and is placed in “*.component.spec.ts”. It looks like this:
// inside a describe callback
let component: NiceComponent; // the type
let fixture: ComponentFixture<NiceComponent>; // from "@angular/core/testing"
let debugEl: DebugElement; // handle to the template
let rawHTML: HTMLElement; // handle to the raw html of the template
let mockThatService; // mock object for a dependency
beforeEach(() => {
// if you don't want complex functionality no need to use a spyObj
// a simple object with the props required will do
mockThatService = {};
// creates the module where the component will run
TestBed.configureTestingModule({
declarations: [
component
],
providers: [
{ provide: ThatService, useValue: mockThatService }
]
});
fixture = TestBed.createComponent(NiceComponent);
component = TestBed.componentInstance;
debugEl = TestBed.debugElement;
rawHTML = TestBed.nativeElement;
// runs change detection so the template is kept updated with the component
fixture.detectChanges();
})
Deep & Shallow tests #
A shallow integration test just includes the component that is being tested while a deep test will also include the necessary children component (but may not test them). A deep test is heavy because of dependencies but it is complete.
When using a shallow test, it is possible to ignore the errors caused by dependencies on other components. This is done by adding a schema to the testing module like so:
TestBed.configureTestingModule({
declarations: [/* wtv */],
providers: [/* wtv */],
schemas: [
NO_ERRORS_SCHEMA // from angular core
]
})
Debugging #
This can be a pain. Here are some tools to help:
- Augury can present info neatly in dev tools.
- ngx-logger is a nice logger apparently.
- Put
debuggerin your TS code so it can be paused in the browser.