Page 14 - MSDN Magazine, November 2018
P. 14
Figure 1 The SpeakerService Class That Didn’t Make Any HTTP Requests
what allows you to supply any sort of compatible service back end (real or mocked) to the environment.
Typically, the service does a little bit more than what’s in the sim- ple SpeakerService, and it’s a pain to have to comment out and/or replace the “real” one with a fake one that does nothing, so this is where using a “mock” service works better. Angular has a useful construct called the “Spy” that can insert itself into a regular service and override certain methods to provide a mocked result:
it('should be able to get a list of Speakers', inject([SpeakerService], (service: SpeakerService) => { spy = spyOn(service, 'getSpeakers').and.returnValues([]);
var speakers: Speaker[] = service.getSpeakers(); expect(speakers).toBeDefined(); expect(speakers.length).toBe(0);
}));
By using the Spy, you can “override” the method being invoked in the test in order to provide whatever values you want to get back.
Component Tests
A large part of building an Angular application, however, is build- ing components that can appear on the page, and it’s important to be able to test those, too. To get an even better understand- ing of testing a visual component, let’s start with a simple on/off toggle-switch type component:
@Component({
selector: 'app-toggle',
template: `<button (click)="clicked()">
I am {{isOn ? "on" : "off" }} -- Click me! </button>`,
styleUrls: ['./toggle.component.css']
})
export class ToggleComponent {
public isOn = false;
public clicked() { this.isOn = !this.isOn; } }
To test this, you can literally ignore the DOM entirely and just examine the state of the component when various actions are invoked:
it('should toggle off to on and off again', () => { const comp = new ToggleComponent(); expect(comp.isOn).toBeFalsy();
comp.clicked();
expect(comp.isOn).toBeTruthy(); comp.clicked(); expect(comp.isOn).toBeFalsy();
});
Angular has a useful construct called the “Spy” that can insert itself into a regular service and override certain methods to provide a mocked result.
This doesn’t check the DOM at all, however, which could hide some critical bugs. Just because the “isOn” property has changed doesn’t mean the template has rendered the property correctly, for example. To check for this, you can get hold of the component instance created by the fixture, and examine the DOM rendered for it, like so:
@Injectable()
export class SpeakerService {
private static speakersList : Speaker[] = [ new Speaker(1, "Ted", "Neward", 47,
"Ted is a big geek living in Redmond, WA"), new Speaker(2, "Brian", "Randell", 47,
"Brian is a high-profile speaker and developer of 20-plus years.
He lives in Southern California."), new Speaker(3, "Rachel", "Appel", 39,
"Rachel is known for shenanigans the world over. She works for Microsoft."), new Speaker(4, "Deborah", "Kurata", 39,
"Deborah is a Microsoft MVP and Google Developer Expert in Angular,
and works for the Google Angular team."), new Speaker(5, "Beth", "Massi", 39,
"Beth single-handedly rescued FoxPro from utter obscurity
and currently works for Microsoft on the .NET Foundation.") ]
public getSpeakers() : Speaker[] { return SpeakerService.speakersList; } public getSpeakerById(id : number) : Speaker {
return SpeakerService.speakersList.find( (s) => s.id == id); }
}
Figure 2 Testing the SpeakerService Class
describe('SpeakerService', () => { beforeEach(() => {
TestBed.configureTestingModule({ providers: [SpeakerService]
}); });
it('should be able to inject the SpeakerService', inject([SpeakerService], (service: SpeakerService) => {
expect(service).toBeTruthy();
}));
it('should be able to get a list of Speakers', inject([SpeakerService], (service: SpeakerService) => { expect(service.getSpeakers()).toBeDefined(); expect(service.getSpeakers().length).toBeGreaterThan(0);
})); });
test, because they tend to provide behavior and very little state. In the case of services that provide some behavior on their own, such as formatting or some simple data transformation, testing is easy and reminiscent of testing a class like Speaker. But services also frequently have to interact with the world around them in some fashion (such as making HTTP requests, as the SpeakerService did a few columns back), which means testing them becomes trickier if you don’t want to have to include the dependencies. Actually sending requests out over HTTP, for example, would make the tests subject to the vagaries of network communication or server outages, which could yield some false-negative failures and make the tests less deterministic. That would be bad.
It’s for these situations that Angular makes such heavy use of dependency injection.
For example, let’s start with the version of SpeakerService that didn’t make any HTTP requests, as shown in Figure 1.
This version is trivial to test, because it’s synchronous and requires no outside dependencies, as shown in Figure 2.
Notice the “inject” calls in each test? This is basically how Angular manages the dependency injection in the testing environment; it’s
10 msdn magazine
The Working Programmer

