Angular Unit Testing 101

Angular Unit Testing 101

In this post, we'll be talking about Angular unit testing, using Karma and Jasmine. By the end of this post, you should feel comfortable writing specs to test your Angular components, directives, pipes, and services as well as learning techniques to test synchronous and asynchronous behaviors.

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.

Before we start

First things first, let's talk about some of the basics and terminologies of testing. It'll help us establish a mental model of how things work so we will be able to understand the later parts a little bit better.

Terminologies

Automated testing

It's the practice of writing code to test our code, and then run those tests.

Unit test

There are 3 types of tests: unit test, integration tests, and end-to-end (e2e) tests. A unit test or UT is the procedure to check the proper functioning of a specific part of a software or a portion of a program.

Karma

Karma is a test runner. It will automatically create a browser instance, run our tests, then gives us the results. The big advantage is that it allows us to test our code in different browsers without any manual change in our part.

The pattern that Karma uses to identify test files is <filename>.spec.ts. This is a general convention that other languages use. If for some reason you want to change it, you can do so in the test.ts file.

Jasmine

Jasmine is a popular Javascript testing framework. It comes with test doubles by using spies (we'll define what is a spy later), and assertions built into it out of the box.

Jasmine provides a lot of useful functions to write tests. The three main APIs are:

  1. Describe(): It's a suite of tests
  2. it(): Declaration of a single test
  3. expect(): Expect something to be true for example

Mock

Mock objects are fake (simulated) objects that mimic the behavior of real objects in controlled ways.

Fixture

A fixture is a wrapper around an instance of a component. With a fixture, we can have access to a component instance as well as its template.

Spy

Spies are useful for verifying the behavior of our components depending on outside inputs, without having to define those outside inputs. They're most useful when testing components that have services as a dependency.

Basics

The Angular CLI downloads and installs everything you need to test an Angular application with the Jasmine test framework. All you need to do to start testing is to run the following command:

ng test

This command builds the app in watch mode and launches Karma.

Angular automated testing

Skeleton of a test

Using the three Jasmine APIs mentioned above, a skeleton of a unit test should look like this:

describe('TestSuitName', () => {
  // suite of tests here

it('should do some stuff', () => { // this is the body of the test }); });

When testing, there's a pattern that became almost a standard across the developer community, called AAA (Arrange-Act-Assert). AAA suggests that you should divide your test method into three sections: arrange, act and assert. Each one of them only responsible for the part in which they are named after.

So the arrange section you only have code required to set up that specific test. Here objects would be created, mocks setup (if you are using one) and potentially expectations would be set. Then there is the Act, which should be the invocation of the method being tested. And on Assert you would simply check whether the expectations were met.

Following this pattern does make the code quite well structured and easy to understand. In general lines, it would look like this:

  it('should truncate a string if its too long (>20)', () => {
    // Arrange
    const pipe = new TroncaturePipe();

// Act
const ret = pipe.transform('1234567890123456789012345');

// Assert
expect(ret.length).toBeLessThanOrEqual(20);

});

Configuration & instantiation

In order to access methods of the component we want to test, we first need to instantiate it.

Jasmine comes with an API called beforeAll() which is called once before all the tests.

The thing is if we instantiate our component inside this function our tests won't be isolated because the component properties could be changed by each test, and therefore, a first test could influence the behavior of a second test.

To solve that problem, Jasmine has another API called beforeEach(), which is very useful as it lets our tests to be run from the same starting point and thus to be run in isolation. 

So, using this API, our test should look something like this:

describe('componentName', () => {
  // suite of tests here

beforeEach(() => { TestBed.configureTestingModule({ declarations: [myComponent], });

fixture = TestBed.createComponent(myComponent);
component = fixture.componentInstance;

});

it('should do some stuff', () => { // this is the body of the test

// test stuff here
expect(myComponent.methodOfMyComponent()).not.toBe(true);

}); });

All of a sudden we have a lot of new unknown APIs. Let's have a closer look at what we have here. 

Angular comes with an API for testing testBed that has a methodconfigureTestingModule() for configuring a test module where we can import other Angular modules, components, pipes, directives, or services.

Once our testing module configured we can then instantiate for example the component we want to test.

Components

An Angular component combines an HTML template and a TypeScript class.

So, to test a component we need to create the component's host element in the browser DOM.

To do that we use a TestBed method called createComponent()

This method will create a fixture containing our component instance and its HTML reference. With this fixture, we can access the raw component by calling its property componentInstance and its HTML reference by using nativeElement.

With that, an Angular component test should look like this:

describe('HeaderComponent', () => {
  let component: HeaderComponent;
  let element: HTMLElement;
  let fixture: ComponentFixture<HeaderComponent>;

// * We use beforeEach so our tests are run in isolation beforeEach(() => { TestBed.configureTestingModule({ // * here we configure our testing module with all the declarations, // * imports, and providers necessary to this component imports: [CommonModule], providers: [], declarations: [HeaderComponent], }).compileComponents();

fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance; // The component instantiation 
element = fixture.nativeElement; // The HTML reference

});

it('should create', () => { expect(component).toBeTruthy(); });

it('should create', () => { // * arrange const title = 'Hey there, i hope you are enjoying this article'; const titleElement = element.querySelector('.header-title'); // * act component.title = title; fixture.detectChanges(); // * assert expect(titleElement.textContent).toContain(title); }); });

After setting the title in our test, we need to call detectChanges() so the template is updated with the new title we just set (because binding happens when Angular performs change detection).

Pipes

Because a pipe is a class that has one method, transform, (that manipulates the input value into a transformed output value), it's easier to test without any Angular testing utilities.

Bellow an example of what a pipe test should look like:

describe('TroncaturePipe', () => {
  it('create an instance', () => {
    const pipe = new TroncaturePipe(); // * pipe instantiation
    expect(pipe).toBeTruthy();
  });

it('truncate a string if its too long (>20)', () => { // * arrange const pipe = new TroncaturePipe(); // * act const ret = pipe.transform('123456789123456789456666123'); // * asser expect(ret.length).toBe(20); }); });

Directives

An attribute directive modifies the behavior of an element. So you could unit test it like a pipe where you only test its methods, or you could test it with a host component where you can check if it correctly changed its behavior.

Here is an example of testing a directive with a host component:

// * Host component:
@Component({
  template: &lt;div [appPadding]="2"&gt;Test&lt;/div&gt;,
})
class HostComponent {}
@NgModule({
  declarations: [HostComponent, PaddingDirective],
  exports: [HostComponent],
})
class HostModule {}

// * Test suite: describe('PaddingDirective', () => { let component: HostComponent; let element: HTMLElement; let fixture: ComponentFixture<HostComponent>;

beforeEach(() => { TestBed.configureTestingModule({ imports: [CommonModule, HostModule], // * we import the host module }).compileComponents();

fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;

fixture.detectChanges(); // * so the directive gets appilied

});

it('should create a host instance', () => { expect(component).toBeTruthy(); });

it('should add padding', () => { // * arrange const el = element.querySelector('div'); // * assert expect(el.style.padding).toBe('2rem'); // * we check if the directive worked correctly }); });

Services

Like pipes, services are often easier to test. We could instantiate them with the new keyword. That's fine for basic services, but if your service has dependencies, it's better to use the TestBed.configureTestingModule API like this:

describe('LocalService', () => {
  let service: LocalService;

beforeEach(() => { TestBed.configureTestingModule({ providers: [LocalService], });

service = TestBed.get(LocalService); // * inject service instance

});

it('should be created', () => { expect(service).toBeTruthy(); });

it('should set the local', () => { // * act service.setLocal('fr'); // * assert expect(service.getLocal()).toBe('fr'); }); });

To get the service instance, we could inject it inside a test by calling TestBed.get() (with the service class as the argument).

Well, with that you should be ready to write tests for your Angular applications. That being said, to tackle some of the common difficulties you might encounter while writing Angular tests, I added small cheatsheets you can find in the next section :)

Cheatsheets

Dealing with HTTP requests

To avoid making HTTP requests at each test, one method consists of providing a fake service that mocks the real one (the one that communicates via HTTP requests). 

Once the fake service is implemented we provide it to the TestBed.configureTestingModule() like this:

class FakeApiService {
  // Implement the methods you want to overload here
  getData() {
    return of({ items: [] }); // * mocks the return of the real method
  }
}
//...
TestBed.configureTestingModule({
  imports: [],
  declarations: [myComponent],
  providers: [
    {
      provide: RealApiService,
      useClass: FakeApiService,
    },
  ],
});
//...

Dealing with the Angular router

To deal with the Router you could either add the RouterTestingModule in the imports of your testing module or you could mock it using the technique we saw in the test above.

Using spies

Spies are an easy way to check if a function was called or to provide a custom return value.

Here is an example of how to use them:

it('should do something', () => {
  // arrange
  const service = TestBed.get(dataService);
  const spyOnMethod = spyOn(service, 'saveData').and.callThrough();
  // act
  component.onSave();
  // assert
  expect(spyOnMethod).toHaveBeenCalled();
});
You could read more about Spies in this great article.

Dealing with asynchronous code

Dealing with promises
it('should do something async', async () => {
  //  * arrange
  const ob = { id: 1 };
  component.selected = ob;
  //  * act
  const selected = await component.getSelectedAsync(); // get the promise value
  //  * assert
  expect(selected.id).toBe(ob.id);
});
Dealing with observables
it('should do something async', (done) => {
  //  * arrange
  const ob = { id: 1 };
  component.selected = ob;
  //  * act
  const selected$ = component.getSelectedObs(); // get an Observable
  //  * assert
  selected$.subscribe(selected => {
    expect(selected.id).toBe(ob.id);
    done(); // let Jasmine know that you are done testing
  });
});
Dealing with timeouts
const TIMEOUT_DELAY = 250;
//...
it('should do something async', (done) => {
  //  * arrange
  const ob = { id: 1 };
  //  * act
  component.setSelectedAfterATimeout(ob);
  // * assert
  setTimeout(() => {
    expect(component.selected.id).toBe(ob.id);
    done(); // let Jasmine know that you are done testing
  }, TIMEOUT_DELAY);
});

Conclusion

So, in this article, we saw that the Angular CLI configures everything for us and we only have to run ng test to start our tests. Then we saw what is an automated test and how to write one using Jasmine and the Angular testing utilities (for components, pipes, directives, and services). Finally, we saw examples of some of the special cases you might encounter when writing tests.

With all this we just scraped the surface of Angular testing, there's so much more to learn about it. That's why this post is the first of an "Angular testing" series. Follow me on twitter @theAngularGuy to get updated when the next posts might come out.

I hope you liked this article, tell me what are your thoughts about automated testing, is it helpful to you or is it more like a struggle?

In the meantime, Happy codding!

Originally published by Mustapha at dev.to

==========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Learn and Understand AngularJS

☞ The Complete Angular Course: Beginner to Advanced

☞ Angular Crash Course for Busy Developers

☞ Angular Essentials (Angular 2+ with TypeScript)

☞ Angular (Full App) with Angular Material, Angularfire & NgRx

☞ Angular & NodeJS - The MEAN Stack Guide

angular web-development

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire Web Developer

Looking for an attractive & user-friendly web developer? HourlyDeveloper.io, a leading web, and mobile app development company, offers web developers for hire through flexible engagement models. You can **[Hire Web...

Why Web Development is Important for your Business

With the rapid development in technology, the old ways to do business have changed completely. A lot more advanced and developed ways are ...

Important Reasons to Hire a Professional Web Development Company

    You name the business and I will tell you how web development can help you promote your business. If it is a startup or you seeking some...

Wondering how to upgrade your skills in the pandemic? Here's a simple way you can do it.

Corona Virus Pandemic has brought the world to a standstill. Countries are on a major lockdown. Schools, colleges, theatres, gym, clubs, and all other public

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.