Setting up our test environment

Our first step will be to download and integrate Jasmine in our project. To do so, go to the console in our project and execute the following npm command that will install the jasmine-core package (including the exact versions of the dependencies required rather than using npm's default semver range operator, hence the --save-exact flag) required for our tests:

$ npm install jasmine-core --save-dev --save-exact

With the package installed, we need to ensure that our project contains the proper typings for the Jasmine global objects and function matchers, so the compiler can recognize the types and thus build our tests:

$ typings install jasmine --save --ambient

With all the libraries in place, let's see how we will put together a spec runner to process our tests and output results.

Implementing our test runner

We are going to run our tests in a browser, and in order to do so we will need to set some base testing providers that are specific to the browser platform. Create a folder named test at the root of our project and save the following file there:

test/setup.ts

import { resetBaseTestProviders, setBaseTestProviders } from '@angular/core/testing';
import { BROWSER_APP_DYNAMIC_PROVIDERS } from "@angular/platform-browser-dynamic";
import {
  TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
  ADDITIONAL_TEST_BROWSER_PROVIDERS
} from '@angular/platform-browser/testing';

resetBaseTestProviders();

setBaseTestProviders(
  TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
  [
    BROWSER_APP_DYNAMIC_PROVIDERS,
    ADDITIONAL_TEST_BROWSER_PROVIDERS
  ]
);

We will import this file into our testing implementation shortly and there is no need to cover it in detail at this point. Let's just say these providers contain DOM adapters required to perform certain operations in Shadow DOM implementations.

Before moving on, it is important to highlight one of the conventions used in this book for Angular 2 unit tests: file naming and spec location. There is a general agreement on saving our test spec files in the same location where the tested module lives. In order to make things even clearer, we will name the spec files after the name of the code unit they test, and will append the .spec suffix to the filename. This way, it becomes easier to locate the tests corresponding to each module, check what is tested and what modules lack a test, and get better acquainted with the code base. Keep in mind that a good test becomes a valuable piece of documentation by itself.

Let's see this naming convention in action by creating a temporary test spec at the root of your project, so we can check whether our runner is working fine:

test.spec.ts

describe('Our test runner', () => {
  it('is alive!', () => {
    expect(true).toBe(true);
  });
});

Let's create our spec runner file now at the root of our project. The spec runner is pretty similar to the main HTML page in regards to the script resources required but also adds on top of that all the required include scripts from Jasmine and the testing bundle from Angular 2. The following implementation also declares an array with string pointers to the locations of the browser testing setup file and the proof of concept test we just built, besides featuring a variable storing the path to the compiled TypeScript files:

spec-runner.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" 
      content="text/html;charset=utf-8">
    <title>Pomodoro App Unit Tests</title>

    <link rel="stylesheet" 
      href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
    <script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
    <script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
    <script src="node_modules/jasmine-core/lib/jasmine-core/boot.js">
    </script>

    <script src="node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>

    <script src="systemjs.config.js"></script>
  </head>
  <body>
    <script>
      // Your typescript compiler 'outDir' parameter value
      var outDir = 'built';

      // Enlist your specs in the following spec collection array,
      // next to the setup import file, all with no file extension
      var specCollection = [
        'test/setup',
        'test.spec' // The test spec we just built
      ];

      // We load all specs asynchronously from the built folder
      // and evaluate their output at once
      Promise.all(specCollection.map(specPath => {
        return System.import(`${outDir}/${specPath}`);
      }))
      .then(window.onload)
      .catch(console.error.bind(console));
    </script>
  </body>
</html>

Check the import scripts and focus on the last block of code. As we said, we define the path to the compiled files (according to our tsconfig.json setup) and an array of string paths to the main browser testing setup and our specs, which is only one at this moment. The System.config implementation is pretty straightforward and reminds of the one we already have for launching our project at index.html. Our last block is a bit more complex and requires some more attention. Basically, we create an array of System.import commands by mapping the setup and spec paths array into another array that combines those paths with the outDir folder location. Each System.import() execution returns a promise, so the resulting array can be executed altogether through a Promise.all() command, triggering a window onload even upon resolving. It is precisely this event that Jasmine is waiting for to render the pass/fail report.

Time to see all this in action! Save the changes, run the compiler to have transpiled versions of the the test setup file and our just created spec, and then run the local web server by browsing to the spec-runner file URL in the browser location bar. You should see a web report like this:

Implementing our test runner

Setting up NPM commands

Testing our modules is an iterative process, so we can ease things a bit in order to make the whole flow smoother. To do so, we can set up some wrapping commands around the common tasks of erasing the contents of the build folder, recompiling the project and triggering a web server pointing to our test runner. As a matter of fact, the package.json file can allocate a test command, which will also trigger pretest and posttest scripts when executed. With this knowledge in hand, let's update the scripts block in our package.json file to include commands that perform all the aforementioned operations:

package.json

...
"scripts": {
  "postinstall": "npm run typings install",
  "tsc": "tsc",
  "tsc:w": "tsc -w",
  "lite": "lite-server",
  "prestart": "tsc",
  "start": "concurrently "npm run tsc:w" "npm run lite" ",
  "typings": "typings",
  "pretest": "rm -rf ./built && npm run tsc",
  "test": "npm run lite --open=/spec-runner.html"
},
...

Whenever you want to run the whole batch of tests on our application, just go to the console and run npm test at the project location prompt. Remember that the compiler is not executed in watch mode, so during your development sessions it is safe to run npm start instead and load the spec runner in a separate browser window to check the evolution of our tests, if required.

We're done with our setup! In the following pages, we will go through different examples of tests structures for components, directives, pipes, services and routes, taking some modules of the pomodoro app project as an example. There is no one-size-fits-all pattern when unit testing Angular 2 modules, but different approaches depending on the subject of test. All in all, we will go finding recurring patterns in our tests and the rest of the implementation will explain by itself, so we will not get into much detail describing what the code does in each example. Ultimately, that is what TDD is all about: explaining how code works by putting it to the test.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset