December 2018

Volume 33 Number 12

[The Working Programmer]

How To Be MEAN: End to End

By Ted Neward | December 2018

Ted NewardWelcome back again, MEANers.

In the last piece, I talked about unit testing, which verifies that the code on the client is working correctly. However, most of the time unit tests look to mock out the back-end interaction, in favor of creating a more deterministic set of test results; that’s useful, but not sufficient, much of the time. Sometimes, you really need to make sure the front end is communicating effectively with the back end, particularly when both are in flux—changes to the JSON exchanged between the two, for example, can each be unit-tested as correct within themselves, but once you start actually passing data across the wire, they can disagree, and break. Nobody wants that, particularly since (as every developer with more than three years’ experience can tell you) it will only happen when you’re doing a demo in front of somebody much higher in the org chart than you.

This is the province of Angular’s end-to-end (e2e) testing, and where unit testing looks to test each component individually and, isolated away from the rest of the system, e2e testing looks to test the system as a whole. This makes e2e testing more brittle, since it depends on a lot more factors, but it’s also just as important as unit testing in the long run.

Back to the Beginnings

As with the unit-testing discussion, Angular tries to make sure that testing is available from the beginning of your greenfield application by building some basic scaffolding in place when the application is first generated (assuming you use the Angular command-line interface [CLI] tool). The e2e directory, off the root of the project, contains the necessary code to run an end-to-end test, so I’m going to give it a spin before doing anything else: “npm run e2e” launches the end-to-end testing, and unlike the unit-testing tooling, e2e runs through a test pass, then halts. The intent here is that the unit-testing tooling should be running near-constantly during development to verify that things are working “in the small,” and the end-to-end tests should be run only when it’s time to verify that no regressions popped up.

The default test is just to verify the scaffolded app’s “Welcome to app!” message, so one easy first change is to modify the App­Component’s title property (remember, from way back when we first started, that property is in app.component.ts) to “SpeakerApp” and check to see that it shows up. (Don’t forget to change the unit test in app.component.spec.ts, too, by the way.) Make that change, make sure the unit tests pass, then run “npm run e2e” again and note the failure. Well, try to note the failure—the browser closes down but the console window in which you ran “npm” will highlight (in red) the test failure.

So, the next step, then, is to modify the end-to-end tests to reflect the change you’ve made to the code.

Of Pages and Objects

End-to-end testing uses the ProtractorJS framework (at protractortest.org) to run the browser in an “automated” mode, and the code to drive Protractor lives in the app.po.ts file. The “ts” suffix should be pretty familiar by now, but the “po” stands for page object, which is a common idiom in the Web end-to-end testing world. A page object is, literally, an object that represents all the actions possible on a given Web page, so the app.po.ts page would represent the application’s home page, according to the naming convention. Sure enough, if you have a look at it in the editor, you see:

import { browser, by, element } from 'protractor';
export class AppPage {
  navigateTo() {
    return browser.get('/');
  }
  getParagraphText() {
    return element(by.css('app-root h1')).getText();
  }
}

Note that the code here isn’t doing any testing—this is literally a bundle of code that serves a utility role for the end-to-end tests, and nothing more. The navigateTo method simplifies bringing this page up in the browser, and the getParagraphText method simply extracts the text for the title by using a CSS selector to find the H1 element out of the DOM that corresponds to the app-root component. As a matter of fact, that’s probably a misnamed method—it really should be called getTitleText, so let’s make that change and do the corresponding change in the actual test code. (Note that if you monkey around with the browser’s title, via the <title> HTML tag, you might want to rethink the method name again, but because this isn’t a production app, I’m not going to stress over it.)

Now open the other file in the e2e directory, app.e2e-spec.ts:

import { AppPage } from './app.po';
describe('full-app App', () => {
  let page: AppPage;
  beforeEach(() => {
    page = new AppPage();
  });
  it('should display welcome message', () => {
    page.navigateTo();
    expect(page.getParagrahText()).toEqual('Welcome to app!');
  });
});

Here you can see the usual Jasmine test format, with the describe method and it functions, and notice how the test fires up an AppPage instance, navigates to it, then tests to see if the paragraph text matches the expectation. I know there are two changes I need to make—one to use the renamed getTitleText method, and the other to change the text to “Welcome to SpeakerApp,” in order to reflect the change in the title. Once those changes are made and “npm run e2e” is invoked, everything’s green again.

Protractor 101

The Protractor Web site has the complete documentation set, but fortunately most of the Protractor API is pretty self-documenting, at least from a high-level perspective. The Protractor home page has an example of a relatively simple end-to-end test that exercises a “to-do” list application, demonstrating how to find elements on the page by using either CSS selectors (element(by.css(…))), model object (element(by.model(…))), or even grabbing a collection of elements via an Angular repeater (element.all(by.repeater(…))), which can then be counted and individually examined, as desired. The Protractor tutorial also mentions that elements can be obtained by HTML identifier (by.id(…)) and by binding, meaning the element is bound to a given variable (by.binding(…)); these are all collectively known as ElementFinders in the Protractor documentation, in case more details are needed.

Once the e2e tests are set up and running, however (and the default Angular CLI scaffolding gives a strong leg up on that, given that it provides a starting point), really, writing e2e tests is about the same as writing any other tests: arrange, act, assert. Given that this is a Javascript-based world, of course you can change the testing framework you use if you so choose, but assuming you work with the same tools out of the box that the CLI establishes, you’re already on your way towards writing a comprehensive set of e2e tests.

One thing that bears mentioning: whether or not you use page objects is entirely your personal choice, but, frankly, given that e2e tests tend to be more “macro” tests (meaning, you typically don’t test just one page or component, but a whole series of steps) and/or user-acceptance-style tests, it’s generally helpful to have page objects in place to make the tests easier to read and—particularly—maintain. Remember, UI tends to be one of those things that shifts often during user demos, and the page object approach can help minimize the “churn” that can happen to e2e tests because of user changes.

Incorporating Server Bits

Given that I suggested that running the end-to-end tests means using the server, and that running the e2e tests should be a pretty common thing, it’s probably a good idea to make sure that running the e2e tests also fires up the server. Otherwise, humans have to remember to run something before running the e2e tests, and that’s usually a recipe for frustration in the long term. One way to do this, of course, is to write a batch file or PowerShell script to run both; another approach is to simply edit the package.json that Angular generated to have the “e2e” part of the file issue the right shell commands to launch a local version of the API server. (It’s not a flawless approach, but it serves for now.) That way, you remain consistent with the Angular CLI conventions.

Assuming that the server parts are in a subdirectory called “server” (and that you’re using the LoopbackJS-based server I wrote back in October, 2017 (msdn.com/magazine/mt826349), then all you need to do is get “npm run” to issue a “node server” command, which will drop into the server directory and issue a “node .,” which is enough to launch the Loopback bits:

{
  "name": "full-app",
  // ...
  "scripts": {
    "ng": "ng",
    "start": "ng server",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "start node server & ng e2e"
  },
  // ...
}

The “start” in front of the “node server” is to tell the shell to spin off node server into a separate command window; this will have the unfortunate side effect of leaving the server window running after the e2e tests finish, but it suffices for now. (If this really creates heartache, replace the two commands with a batch file or PowerShell script, and then you’re in best-of-both-worlds territory.) Now, the server is up and running, and the client can start making calls to it.

Arguably, for this to be a true end-to-end test, the e2e tests should be operating against a shared server, but then you’re back to the problem of non-determinism in the tests. Others may disagree, but I prefer a local server over a remote/shared one for this sort of testing.

Wrapping Up

By the way, although it realistically shouldn’t be a concern by this point, as of this writing, the Angular team just released Angular 7. This means this column has seen at least six significant releases of Angular since its start, and given that none have created any major rewrites on our part, that should give us a certain comfort level in adopting Angular for any long-lived application and, hopefully, addresses a few of the concerns toward adopting Angular in your company or team.

On a logistical note, this column marks the 30th article written on the MEAN stack, and while there’s clearly more I could delve into, the name of the column is “Working Programmer,” not “MEAN Programmer,” and it’s time to move on. Angular’s cool and all, but this could go on ad infinitum, and there are lots of other interesting things out there to explore: new C# features, new F# features, and maybe I’ll even take a dive into the inner workings of the CLR, or a JavaScript engine (either V8, the one that ships with NodeJS, or Chakra, Microsoft’s version of V8), for a bit. Before ending, though, let’s be gracious consumers, and say, thanks, Angular, for all the good memories over the last few years, and we’ll be seeing you around. Keep up the good work, and we’ll drink a toast to you when you hit Angular 10 and 15 and 20 and so on. For now, though, fare well and smooth sailing.

As always, happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker, and mentor, currently working as a director of Engineering and the director of Developer Relations at Smartsheet.com. He’s written a ton of articles, authored and co-authored a dozen books, and speaks all over the world. Reach him at ted@tedneward.com or read his blog at blogs.tedneward.com.

Thanks to the following technical expert for reviewing this article: Garvice Eakins (Smartsheet.com)


Discuss this article in the MSDN Magazine forum