Книга: Angular 2 Cookbook
Назад: Creating a minimum viable unit test suite with Karma, Jasmine, and TypeScript
Дальше: Writing a minimum viable end-to-end test suite for a simple application

Writing a minimum viable unit test suite for a simple component

Unit tests are the bread and butter of your application testing process. They exist as a companion to your source code, and most of the time, the bulk of your application tests will be unit tests. They are lightweight, run quickly, are easy to read and reason about, and can give context as to how the code should be used and how it might behave.

Setting up Karma, Jasmine, TypeScript, and Angular 2 along with all the connecting configurations between them is a bit of an imposing task; it was deemed to be out of the scope of this chapter. It's not a very interesting discussion to get all of them to work together, especially since there are already so many example projects that have put together their own setups for you. It's far more interesting to dive directly into the tests themselves and see how they can actually interact with Angular 2.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

This recipe will assume you are using a working Angular 2 testing environment. The one provided in the application generated by the Angular CLI is ideal. Tests can be run in this environment with the following command inside the project directory:

 ng test 

Begin with the following component:

[src/app/article/article.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'app-article',     template: `       <h1>         {{ title }}       </h1>     `   })   export class ArticleComponent {     title: string = 'Captain Hook Sues Over Spork Snafu';   }   

Your goal is to entirely flesh out article.component.spec.ts to test this class.

How to do it...

The simplest possible test you can think of is the one that will simply check that you are able to instantiate an instance of ArticleComponent. Begin with that test:

[src/app/article/article.component.spec.ts]      import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });   });   

Nothing tricky is going on here. Since ArticleComponent is just a plain old TypeScript class, nothing is preventing you from creating an instance and inspecting it in the memory.

However, for it to actually behave like an Angular 2 component, you'll need some other tools.

Using TestBed and async

When you try to puppet an Angular 2 environment for the component in a test, there are a number of considerations you'll need to account for. First, Angular 2 unit tests heavily rely upon TestBed, which can be thought of as your testing multitool.

The denomination of unit tests when dealing with a component involves ComponentFixture.  TestBed.createComponent() will create a fixture wrapping an instance of the desired component.

Tip

The need for fixtures is centered in how unit tests are supposed to work. An ArticleComponent does not make sense when instantiated as it was with the initial test you wrote. There is no DOM element to attach to, no running application, and so on. It doesn't make sense for the component unit tests to have an explicit dependency on these things. So, ComponentFixture is Angular's way of letting you test only the concerns of the component as it would normally exist, without worrying about all the messiness of its innate dependencies.

The TestBed fixture's asynchronous behavior mandates that the test logic is executed inside an async() wrapper.

Tip

The async() wrapper simply runs the test inside its own zone. This allows the test runner to wait for all the asynchronous calls inside the test to complete them before ending the test.

Begin by importing TestBed and async from the Angular testing module and put together the skeleton for two more unit tests:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        it('should have correct title', async(() => {     }));        it('should render title in an h1 tag', async(() => {     }));   });   

Now that you have the skeletons for the two tests you'd like to write, it's time to use TestBed to define the test module. Angular 2 components are paired with a module definition, but when performing unit tests, you'll need to use the TestBed module's definition for the component to work properly. This can be done with TestBed.configureTestModule(), and you'll want to invoke this before each test.

Jasmine's describe allows you to group beforeEach and afterEach inside it, and it is perfect for use here:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        describe('Async', () => {       beforeEach( () => {         TestBed.configureTestingModule({           declarations: [             ArticleComponent           ],         });       });          it('should have correct title', async(() => {       }));          it('should render title in an h1 tag', async(() => {       }));     });   });   

Creating a ComponentFixture

TestBed gives you the ability to create a fixture, but you have yet to actually do it. You'll need a fixture for both the async tests, so it makes sense to do this in beforeEach too:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     let fixture;        it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        describe('Async', () => {       beforeEach( () => {         TestBed.configureTestingModule({           declarations: [             ArticleComponent           ],         });            fixture = TestBed.createComponent(ArticleComponent);       }));          afterEach(() => {         fixture = undefined;       });          it('should have correct title', async(() => {       }));          it('should render title in an h1 tag', async(() => {       }));     });   });   

Tip

Here, fixture is assigned to undefined in the afterEach teardown. This is technically superfluous for the purpose of these tests, but it is good to get into the habit of performing a robust teardown of shared variables in unit tests. This is because one of the most frustrating things to debug in a test suite is test variable bleed. After all, these are just functions running in a sequence in a browser.

Now that the fixture is defined for each test, you can use its methods to inspect the instantiated component in different ways.

For the first test, you'd like to inspect the ArticleComponent object itself from within ComponentFixture. This is exposed with the componentInstance property:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     let expectedTitle = 'Captain Hook Sues Over Spork Snafu';     let fixture;        it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        describe('Async', () => {       beforeEach(async(() => {         TestBed.configureTestingModule({           declarations: [             ArticleComponent           ],         });         fixture = TestBed.createComponent(ArticleComponent);       }));          afterEach(() => {         fixture = undefined;       });          it('should have correct title', async(() => {         expect(fixture.componentInstance.title)           .toEqual(expectedTitle);       }));          it('should render title in an h1 tag', async(() => {       }));     });   });   

For the second test, you want access to the DOM that the fixture has attached the component instance to. The root element that the component is targeting is exposed with the nativeElement property:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     let expectedTitle = 'Captain Hook Sues Over Spork Snafu';     let fixture;        it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        describe('Async', () => {       beforeEach(async(() => {         TestBed.configureTestingModule({           declarations: [             ArticleComponent           ],         });         fixture = TestBed.createComponent(ArticleComponent);       }));          afterEach(() => {         fixture = undefined;       });          it('should have correct title', async(() => {         expect(fixture.componentInstance.title)           .toEqual(expectedTitle);       }));          it('should render title in an h1 tag', async(() => {         expect(fixture.nativeElement.querySelector('h1')           .textContent).toContain(expectedTitle);       }));     });   });   

If you run these tests, you will notice that the last test will fail. The test sees an empty string inside <h1></h1>. This is because you are binding a value in the template to a component member. Since the fixture controls the entire environment surrounding the component, it also controls the change detection strategy—which, here, is to not run until it is told to do so. You can trigger a round of change detection using the detectChanges() method:

[src/app/article/article.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {ArticleComponent} from './article.component';      describe('Component: Article', () => {     let expectedTitle = 'Captain Hook Sues Over Spork Snafu';     let fixture;        it('should create an instance', () => {       let component = new ArticleComponent();       expect(component).toBeTruthy();     });        describe('Async', () => {       beforeEach(async(() => {         TestBed.configureTestingModule({           declarations: [             ArticleComponent           ],         });         fixture = TestBed.createComponent(ArticleComponent);       }));          afterEach(() => {         fixture = undefined;       });          it('should have correct title', async(() => {            expect(fixture.componentInstance.title)              .toEqual(expectedTitle);       }));          it('should render title in an h1 tag', async(() => {         fixture.detectChanges();         expect(fixture.nativeElement.querySelector('h1')           .textContent).toContain(expectedTitle);       }));     });   });   

With this, you should see Karma run and pass all three tests.

How it works...

When it comes to testing components, fixture is your friend. It gives you the ability to inspect and manipulate the component in an environment that it will behave comfortably in. You are then able to manipulate the instances of input made to the component, as well as inspect their output and resultant behavior.

This is the core of unit testing: the "thing" you are testing—here, a component class—should be treated as a black box. You control what goes into the box, and your tests should measure and define what they expect to come out of the box. If the tests account for all the possible cases of input and output, then you have achieved 100 percent unit test coverage of that thing.

See also

  • Creating a minimum viable unit test suite with Karma, Jasmine, and TypeScript gives you a gentle introduction to unit tests with TypeScript
  • Unit testing a synchronous service demonstrates how an injection is mocked in unit tests
  • Unit testing a component with a service dependency using Stubs shows how you can create a service mock to write unit tests and avoid direct dependencies
  • Unit testing a component with a service dependency using Spies shows how you can keep track of service method invocations inside a unit test
Назад: Creating a minimum viable unit test suite with Karma, Jasmine, and TypeScript
Дальше: Writing a minimum viable end-to-end test suite for a simple application

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already