Testing Strategies in Angular

There are lots of different libraries available for writing tests, several ways to run them, lots of different frameworks that are available and our goal is to give you a good set of defaults with our tools like CLI, and with our test repositories, but we want them to be flexible. If you feel that such tools are easy, you can work with them.

Lets start by diving right into an example. The classical example for dependency injection is that we have a car which has the engine and some tyres. Plain Javascript of creating a new car is simple.

1.jpg

We can write all of the code needed for this but you are not really testing your module setup and also not focusing on testing your DI setup and still doing the extra work. In this scenario comes Test Bed.

Test Bed

The test bed is the main entry point for all of APIs that Angular provides for testing. It is a collection of static methods that configure override, and use that testing module for every test. It consists of some set-up functions like explained in the below example. Once the application has been bootstrapped, it is running on the browser platform.

Testbed.configureTestingModule({

// usual module props

})

+

inject()

Example

Back to our car example, we can use Jasmine function and configure the testing module.

Testing Components

Components are complicated when there is a template which is interacting with code, it's not obvious how to set that up by default. Test bed gives the ability to compile components which are just components in the test module and asynchronously create components once they are compiled. Function TestBed.createComponent() returns a component fixture which has a pack of utilities for dealing with issues that Angular might need to do for a component.

3.jpg

Two primary points are there, first is debugging the elements and secondly, a component instance from the fixture. Debugging an element is an Angular representation of an abstract element, if a coder is writing a component library it might be not only be used for the web, but also for other platforms.

A test can test at the debug level and a similar approach can be applied to references which contain lots of same information. A native element can be determined through this approach. For instance, in tic-tac-toe game there is a component and user can click anywhere in the boxes.

gif1.gif

The component will detect when something has ended or the user is able to utilize the keyboard for interaction. At that time, we can press a row and then column and it is listening for key events on the window. Press 103 and continue playing the game. Two different components are noticed here and the tic-tac-toe board is handling the key events.

It exports some outputs to the user interface which has the player names, lists whose turn it is, produces output and let the user reset when the game is finish.

Function Async() for Unit Tests

Function async has been developed for unit tests and it has one function where the developer has to manually call when they are finished. We have also tried to return values from tests which have the issues of not calling the inner promises. In such cases, the coder is not sure when to call the promises before or after the test has been completed. Async is a solution to such problems by using zones in order to understand the asynchronous events. This way the developer doesn't have to manually keep track of them. It is essential for every test that when every time it's invoked, a new asynchronous test zone is created which handles those queues of micro tests and macro tests, and when the list is empty, the test will be completed. It has been optimized for working with tools like Jasmine and Mocha only if it can wrap around a test function. In Jasmine, there is a special handling case for functions to be run in the same zone when you have a block and in a block, if a component has been setup inside and tests are run within the component. All of them will be in the same zone. The asynchronous events are also in the zone of the block which will exit early. A couple of small files are defined in zone specs which can be easily adapted to another framework that is functional.

// Angular Unit Test Solution

async() and fakeAsync()

it('waits for task to finish', async(() => {}));

// Behind the scenes

let AsyncTestZoneSpec = Zone['AsyncTestZoneSpec'];

var asyncZoneSpec = new AsyncTestZoneSpec();

let testZone = Zone.current.fork(asyncZoneSpec);

testZone.run(testFunction);

// Plus Special Handling for Jasmine

Let's take an example of weather service. In the below code, we are testing that there is a service which gets the temperature and it is an asynchronous service because it needs to call out a meteorologist and it will take some time. This way we can inject the weather service, get the temperature and make assertions on that. In this scenario, calling is not done anywhere and not returning any promises. We have two different asynchronous events going on and we are assured that both of them will complete before test exits.

/Testing a service that returns a promise

it('should get the temperature',

                async(inject([WeatherService], (weather: WeatherService) => {

                weather.getTemp('London').then((temp) => {

                expect(temp).toEqual(16);

                });

                weather.getTemp('Seattle').then((temp) => {

                expect(temp).toEqual(19);

                });

                })));

Other utilities

4.png

1- Utilities for working with observables

Utilities are more prevalent when they are something of a problem for the testing framework. It is because they setup polling timeouts which aren't easily tested with the async utilities we have now. These observables framework itself essentially creates a log of events as a string. It makes a good set-up for testing for Angular.

2- Ergonomics for dealing with async and dispatching events

Ergonomics for new functions and dispatching events can be improved on different browsers. It takes time because it will allow you to use async-await and over language features coming down the pipes to make it bit a cleaner way.

Example Case: When I initiate react component, I need to make 2 different async http calls and wait for their results. These 2 calls will ideally put their data in 2 different reducers. I understand the pattern for making a single async call. However, I don't see any good samples for making multiple async calls, having them execute in parallel and wait for both their results to arrive. What is the redux way of doing this?