Injecting Dependencies and DOM Changes
quote.component.ts
This component relies on the QuoteService
to get a random quote, which it will then display. The class is pretty simple - it only has the getQuote
function that will modify the DOM, therefore it will be our main area of focus in testing.
In order to test this component we need initiate the QuoteComponent
class. The Angular testing library offers a utility called TestBed
. This allows us to configure a testing module where we can provided mocked dependencies. Additionally it will create the component for us and return a component fixture that we can perform testing operations on.
quote.spec.ts
import { QuoteService } from './quote.service';
import { QuoteComponent } from './quote.component';
import { provide, destroyPlatform } from '@angular/core';
import {
async,
inject,
TestBed,
} from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
class MockQuoteService {
public quote: string = 'Test quote';
getQuote() {
return Promise.resolve(this.quote);
}
describe('Testing Quote Component', () => {
beforeEach(() => destroyPlatform());
beforeEach(() => {
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
TestBed.configureTestingModule({
declarations: [
QuoteComponent
],
{ provide: QuoteService, useClass: MockQuoteService }
]
});
fixture = TestBed.createComponent(QuoteComponent);
fixture.detectChanges();
});
it('Should get quote', async(inject([], () => {
fixture.componentInstance.getQuote();
fixture.whenStable()
.then(() => {
fixture.detectChanges();
return fixture.whenStable();
})
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('div').innerText).toEqual('Test quote');
});
});
Testing the QuoteComponent
is a fairly straightforward process. We want to create a QuoteComponent
, feed it a quote and see if it appears in the DOM. This process requires us to create the component, pass in any dependencies, trigger the component to perform an action and then look at the DOM to see if the action is what we expected.
Let's take a look at how this is accomplished with the above unit test.
We use TestBed.initTestingEnvironment
to create a testing platform using BrowserDynamicTestingModule
and platformBrowserDynamicTesting
as arguments, which are also imported from angular and allow the application to be bootstrapped for testing. This is necessary for all unit tests that make use of TestBed
. Notice that this platform is destroyed and reset before each test runs.
We use TestBed.configureTestingModule
to feed in any dependencies that our component requires. Here our component depends on the QuoteService
to get data. We mock this data ourselves thus giving us control over what value we expect to show up. It is good practice to separate component testing from service testing - this makes it easier to test as you are only focusing on a single aspect of the application at a time. If your service or component fails, how will you know which one was the culprit? We inject the QuoteService
dependency using our mock class MockQuoteService
, where we will provide mock data for the component to consume.
In the Should get quote
test we have gotten access to our component through the fixture.componentInstance
property. We then call getQuote
to kickstart our only action in the QuoteComponent
component. We run the test when the fixture is stable by using its whenStable
method which will ensure the promise inside the getQuote()
has resolved, giving the component a chance to set the quote value. We call fixture.detectChanges
to keep an eye out for any changes taking place to the DOM, and use the fixture.debugElement.nativeElement
property to get access to those underlying DOM elements.
Now we can check to see if the DOM rendered by our QuoteComponent
contains the quote that we mocked in through the QuoteService
. The final line attempts to assert that the DOM's div tag contains the mocked quote 'Test Quote' inside. If it does, then our component passes the test and works as expected; if it doesn't, that means our component is not outputting quotes correctly.
We wrap Should get quote
test in async()
. This is to allow our tests run in an asynchronous test zone. Using creates a test zone which will ensure that all asynchronous functions have resolved prior to ending the test.