What/Why Unittest | Unittest with Angular 2+

The more we add features to our software the more it grows in complexity. And as it grows in complexity, more time is required to manually test it. In fact, as we add new features to our applications, the time required to manually test them increases exponentially!
To prevent that we could take advantage of automated testing as it is the best way to increase the effectiveness, efficiency, and coverage of our applications testing.

In this article, we will explore testing components and we will cover the following:

  • What is a unit test?
  • Why write unit tests?
  • How to setup an Angular test?
  • Ok, now how do we write unit tests?

What is a unit test?

A unit test is a type of software testing that verifies the correctness of an isolated section (unit) of code.

Lets say you have a simple addition function:

function sum(...args) {
return args.reduce((total, value) => total + value, 0);
}

This full function can be considered a unit, and therefore your test would verify that this unit is correct. A quick test for this unit could be:

it('should sum a range of numbers correctly', () => {
// Arrange
const expectedValue = 55;
const numsToTest = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Act
const total = sum(...numsToTest);

// Assert
expect(total).toBe(expectedValue);
});

We’re introducing a few concepts here.
The it(...args) is the function that will set up our unit test. It's pretty common testing terminology across Test Runners.

We also introduce the AAA Test Pattern. It’s a pattern that breaks your test into 3 sections.

The first section is Arrange: Here you perform any set up required for your test.

The second section is Act: Here you will get your code to perform the action that you are looking to test.

The third and final section is Assert: Here you will make verify that the unit performed as expected.

In our test above we set what we are expecting the value to be if the function performs correctly and we are setting the data we will use to test the function.

We then call the sum() function on our previously arranged test data and store the result in a total variable.

Finally, we check that the total is the same as the value we are expecting.

If it is, the test will pass, thanks to us using the expect() method.

Note: .toBe() is a matcher function. A matcher function performs a check that the value passed into the expect() function matches the desired outcome. Jasmine comes with a lot of matcher functions which can be viewed here: Jasmine Matchers

But Why Unittest?

As a developer, you are consistently making changes to your codebase. But without tests, how do you know you haven’t made a change that has broken functionality in a different area within your app?

You can try to manually test every possible area and scenario in your application. But that eats into your development time and ultimately your productivity.

It’s much more efficient if you can simply run a command that checks all areas of your app for you to make sure everything is still functioning as expected. Right?

That’s exactly what automated unit testing aims to achieve, and although you spend a little bit more time developing features or fixing bugs when you’re also writing tests, you will gain that time back in the future if you ever have to change functionality, or refactor your code.

Another bonus is that any developer coming along behind you can use the test suites you write as documentation for the code you write. If they don’t understand how to use a class or a method in the code, the tests will show them how!

How do you set up an Angular test?

When you create a new project with the cli (ng new appName), a default component and test file are added. Also, for those that always like a shortcut method like me, a test script is always created alongside any component module (service, component) you create using Angular cli.

This test script which ends with .spec.ts is always added. Let’s take a look at the initial test script file which is the app.component.spec.ts :

Let’s run our first test to make sure nothing has broken yet:

ng test

You might be wondering, how can we simulate a user behavior by simply writing a test, even though the project is being rendered in a browser? As we proceed, I will demonstrate how we can simulate the app running on a browser.

By default, Angular runs on Karma which is a test runner that runs the unit tests snippet like the above app.component.spec.ts file. Karma also ensures the result of the test is printed out either in the console or in file log. Other test runners are mocha, jasmine etc.

How to write and how does the test run?

The testing package has some utilities (TestBed, async). TestBed is the main Angular utility package.

The describe container contains different blocks (it, beforeEach, xitetc). The beforeEach runs before any other block while others do not depend on each other to run.

From the app.component.spec.ts file, the first block is the beforeEach inside the container (describe). This is the only block that runs before any other block (it). The declaration of the app module in app.module.ts file is simulated (declared) in the beforeEach block. The component (AppComponent) declared in the beforeEach block is the main component we want to have in this testing environment. The same logic applies to other test declaration.

The compileComponents object is called to compile your component’s resources like the template, styles etc. You might not necessarily compile your component if you are using webpack:

Now that the component has been declared in the beforeEach block, let’s check if the component is created.

Thefixture.debugElement.componentInstance creates an instance of the class (AppComponent) . We will test to see if the instance of the class is truly created or not using toBeTruthy :

The third block demonstrates how you can have access to the properties of the created component(AppComponent). The only property added by default is the title. You can easily check if the title you set has changed or not from the instance of the component (AppComponent) created:

The fourth block demonstrates how the test behaves in the browser environment. After creating the component, an instance of the created component (detectChanges) to simulate running on the browser environment is called. Now that the component has been rendered, you can have access to its child element by accessing the nativeElelment object of the rendered component (fixture.debugElement.nativeElement):

Now that you have familiarized yourself with the basics of testing a component. Let’s test theQuote application.

How to test a service(QuoteService)?

Services often depend on other services that Angular injects into the constructor. In many cases, it easy to create and inject these dependencies by adding providedIn: root to the injectable object which makes it accessible by any component or service:

Here are a few ways to test the QuoteService class:

In the first block, beforeEach, an instance of QuoteService is created to ensure its only created once and to avoid repetition in other blocks except for some exceptional cases:

The first block tests if the post model QuoteModel(text, date) is created into an array by checking the length of the array. The length of the quoteList is expected to be 1:

The second block creates a post in an array and removes it immediately by calling removeQuote in the service object. The length of the quoteList is expected to be 0.

How to test a component (QuotesComponent)

The service is injected into the QuoteComponent in order to have access to its properties which will be needed by the view:

The first two blocks in the describe container run consecutively. In the first block, the FormsModule is imported into the configure test. This ensures the forms related directives like ngModel can be used.

Also, the QuotesComponent is declared in the configTestMod similarly to how the components are declared in ngModule residing in the appModule file. The second block creates a QuoteComponent and its instance which would be used by the other blocks:

This block tests if the instance of the component that is created is defined:

The injected service handles the manipulation of all operations (add, remove, fetch). The quoteService variable holds the injected service (QuoteService). At this point, the component is yet to be rendered until the detectChangesmethod is called:

Now let’s test if we can successfully create a post. The properties of the component can be accessed upon instantiation, so the component rendered detects the new changes when a value is passed into the quoteText model. The nativeElement object gives access to the HTML element rendered which makes it easier to check if the quote added is part of the texts rendered:

Apart from having access to the HTML contents, you can also get an element by its CSS property. When the quoteText model is empty or null, the button is expected to be disabled:

Just like the way we access an element with its CSS property, we can also access an element by its class name. Multiple classes can be accessed at the same time using By e.g By.css(‘.className.className’) .

The button clicks are simulated by calling the triggerEventHandler . The event type must be specified which ,in this case, is click. A quote displayed is expected to be deleted from the quoteList when clicked on:

I hope this article would help you to understand unit testing better and help you to understand a better way to write unit test with Angular. Thanks!

Coder