Test Angular Components with Jasmine, Karma, and the Test Wrapper Pattern
Automated testing has become a great way protect applications from new bugs. Let’s look at a pattern that can be used to test components.
Prerequisites/Assumptions
You have at least a boilerplate Angular application (one generated using ng new <app-name>
will work), set up with Jasmine and Karma. The code was written using Angular 11 and Angular CLI - the Test Wrapper pattern should work with any supported version of Angular. It is also expected that you will have Node and Git bash, set up on your machine.
Example Component
First step is to generate a simple component using the command ng generate component simple-sample
. When the component is generated, you can delete the HTML and CSS (or whatever style file) is generated. Style won’t be used and the template will be in the Typescript file itself.
The component will be, like the name suggests, simple. It will have two Inputs, a boolean and a string, and an output emitter, which will emit a string. The component will show a title passed in by the input, and have a default title when no input is provided. It will also have two buttons that are enabled/disabled based on the input boolean. When a button is clicked an output will be emitted to the parent component. Here is the component’s Typescript file.
Test Wrapper Component
When you run the generate
command, you’ll get a boilerplate spec file to build tests. The file is set up with a component
and fixture
variable. The component variable is the component being tested. The fixture variable is a testing tool used to apply changes to the component and see how the changes affect the state of the component. Here is what the spec file looks like when it is created by the generate command.
The first addition to the spec file you’ll want to do is create the TestWrapperComponent. The wrapper component is just that, it is a container that wraps the component we are testing with all the inputs, handled outputs, and a flag the acts as a switch that will show and hide the component (default value to not show).
At the beginning of each test case the show flag defaults to false. This means the component that is being tested will not be shown. The first thing done in each test case is set the input values that will be required for the tests. This includes turning on the show flag. Once everything is set, then detectChanges()
is called using the fixture tool in order to refresh the component. This will cause every test to initial the component being tested, with ngOnInit and the constructor run for each test case.
The show/hide switch is key to this pattern. It will allow the component being tested to be reset before each test case so that the component will run through the life cycle functions.
Test Cases
Before starting to write actual tests, there should be several test cases (scenarios), in which to test on the SimpleSampleComponent. Since the component does not have a lot going on, there is not a lot of to test.
The following aspects can be tested; default behavior of the component when the Wrapper shows the testing component, clicking buttons will emit the expected values, disabled buttons being clicked do not emit events, and changing inputs after the component is initialized will update the component accordingly.
With these as scenarios to test, here are the list of test cases to go over.
#01 - undefined inputs, show default title
#02 - someBoolean true, click right button, event is "right"
#03 - default inputs, try click right button, event is ""
#04 - some title updated, span displays specific title
#05 - default inputs, click left button, event is "left"
#06 - update someTitle input after first shown, show updated title
Update Test Configuration
Now that there is a TestWrapperComponent, and a list of test cases, we can configure the spec file for testing. When Angular generates a spec file, the component
and fixture
variables are set to be type of a SimpleSampleComponent.
For the Test Wrapper pattern, we will set the type to be the TestWrapperComponent. In the beforeEach()
function, the function that is called before each test case, the fixture is recreated using the TestBed class. That reset will also have to be updated to use the TestWrapperComponent instead of the SimpleSampleComponent. The TestWrapperComponent will have to be declared in the test configuration set through the TestBed configureTestingModule()
function.
Writing Test Cases
Once you have the configurations set, the test cases can be written. There are essentially two types of test cases for the SimpleSampleComponent. One testing the title being displayed in the component, the other testing the events resulting from a button click based on the state of the component.
In both types of cases, the fixture is debugging property, nativeElement
, is used to query the Wrapper component element for what will be rendered in the DOM. The nativeElement
can be queried using either querySelector()
or querySelectorAll()
functions, just like a document
on a page in a browser.
When testing for a specific title to appear in the DOM, a CSS selector is used in the querySelector()
to grab an Element. Then the innerHTML
string value of the queried Element is trimmed and checked against the expected value.
For the button tests, the CSS selector will be used to grab a ButtonElement. The that button element has a click()
performed on it, and there is a value that will be emitted out and handled by the TestWrapperComponent.
Here is the full spec file, with the TestWrapperComponent, configuration set, and all the test cases implemented.
Results
Karma Browser
Now the tests can be run with the npm test
command. Running this command from a boilerplate Angular project will start the Karma test runner and open a browser. The browser will display the following.
Code Coverage
The test coverage for the tests can also be check using the command, ng test --no-watch --code-coverage
. When the command is run in the command line, the component has 100% statement coverage.
Conclusion
Writing unit tests is a good way to protect you application from introducing bugs when refactoring. Angular provides a good boilerplate for writing testings with both Jasmine framework and Karma test runner. Using the Test Wrapper pattern in your tests is a good way to fully test the life cycle of a component and easily test how changes in a parent will effect the component you are testing.
Try using the pattern next time you generate a component. Good luck, and happy coding!
More content at plainenglish.io