angular-cn/public/docs/ts/latest/testing/testing-an-angular-pipe.jade

200 lines
8.2 KiB
Plaintext
Raw Normal View History

include ../_util-fns
:marked
Well test an Angular pipe in this chapter.
An Angular pipe is a declarative way in HTML to transform some input into some displayable output.
We don't have a pipe though, since in Tour of Heroes we didn't create any pipes. It uses a pipe though, the `uppercase` pipe that comes with Angular 2.
We can make our own `my-uppercase` pipe that does exactly the same as the `uppercase` pipe and test that.
Since we're getting ready to write some code we want to test, let's take this opportunity to talk just a little bit about [Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development). There's a lot written about this topic so we don't want to have an exhaustive description here, but rather a practical application.
We already know *exactly* what we want the `uppercase` pipe to do. We could say our ...expectations... of it are very well defined.
We always use expectations our expectations to guide development, but sometimes it's hard to see the forest for the trees when we're right in the middle of coding. This is especially evident in larger tasks.
So one thing we can do is put those expectations down as cold hard test code. We were going to test things manually anyway, so doing it *before* we have even one line of code isn't going to hurt.
Worst thing that can happen is have that test fail, but on the way to fixing it we'll end up creating our pipe. So in a sense, the failing test will *tell you what it wants* to pass.
We're just putting down expectations, nothing more. If we were to put them down on paper, they would look like this:
```
MyUppercasePipe
transforms "abc" to "ABC"
transforms "abc def" to "ABC DEF"
leaves "ABC DEF" unchanged
```
All we need to know to put down our expectations as code is how a pipe class looks like from the outside. From the [pipe developer guide](pipes#custom-pipes) we know that a pipe implements a `transform` method.
Putting it down as Jasmine expectations, they would look something like this:
+makeExample('testing/ts/app/my-uppercase.pipe.spec.ts', 'expectations')
:marked
In this chapter we will:
- create a test before creating a class
- load multiple test files in our test harness, using system.js
- add the Angular 2 library to our test harness
- watch the new test fail, and fix it
2015-10-10 06:29:34 -04:00
.callout.is-helpful
header Prior Knowledge
:marked
The Unit Testing chapters build upon each other. We recommend reading them in order.
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
we introduced in the [QuickStart](../quickstart.html) and
the [Tour of Heroes](../tutorial/) tutorial
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
:marked
## Add another spec file
**Create** a `my-uppercase.pipe.spec.ts` in `app/`.
**Stop and restart the TypeScript compiler** to ensure we compile the new file.
**Add** the following lines of rather obvious Jasmine test code.
+makeExample('testing/ts/app/my-uppercase.pipe.spec.ts', 'base-pipe-spec', 'app/my-uppercase.pipe.spec.ts')
:marked
Note that each test is short (one line in our case).
It has a clear label that accurately describes the test. And it makes exactly one expectation.
Anyone can read these tests and understand quickly what the test does and what the pipe does.
If one of the tests fails, we know which expected behavior is no longer true.
Well have little trouble maintaining these tests and adding more like them as we encounter new conditions to explore.
Thats the way we like our tests!
## Add this spec to `unit-tests.html`
Now lets wire our new spec file into the HTML test harness.
Open `unit-tests.html`. Find `System.import('app/hero.spec')`.
Hmm. We cant just add `System.import('app/my-uppercase.pipe.spec')`.
The first `System.import` returns a promise as does this second import.
We cant run any of the Jasmine tests until **both imports are finished**.
Fortunately, we can create a new `Promise` that wraps both import promises and waits
for both to finish loading.
+makeExample('testing/ts/unit-tests-4.html', 'promise-all')(format=".")
:marked
We have a pattern for adding new tests.
In future, when we add a new spec, we add another `System.import('app/some.spec')` to
the array argument passed to `Promise.all`.
Try it. The browser should refresh and show the following in the console:
code-example(format="" language="html" escape="html").
GET http://localhost:8080/app/my-uppercase.pipe.js 404 (Not Found)
:marked
Our test failed, as expected. We're importing something that doesn't exist and our test fails saying that. All is going according to plan.
:marked
## The pipe, if you please
The test is asking for a pipe, and we shall deliver.
**Create** a `my-uppercase.pipe.ts` in `app/`.
**Stop and restart the TypeScript compiler** to ensure we compile the new file.
**Add** a basic pipe that doesn't do anything. We know how to make strings uppercase, but we since we're letting the test take the lead let's wait for it to tell us what's next. Maybe it'll surprise us.
+makeExample('testing/ts/app/my-uppercase.pipe.1.ts', null, 'app/my-uppercase.pipe.ts')
:marked
Reload our test page and...
code-example(format="" language="html" escape="html").
GET http://localhost:8080/angular2/core 404 (Not Found)
:marked
## The Angular library, if you please
Looking back at `unit-tests.html` we realize that we have not loaded the Angular library.
Yet we were able to load and test the applications `Hero` interface.
**We were lucky!** The `Hero` interface has no dependence on Angular.
If it had depended on Angular, wed still be staring at the Jasmine “big-time fail” screen:
figure.image-display
img(src='/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png'
alt="Jasmine's' big time fail screen")
:marked
We are writing an Angular application after all and
we were going to need Angular sooner or later. That time has come.
`MyUppercasePipe` depends on Angular as is clear in the first few lines:
+makeExample('testing/ts/app/my-uppercase.pipe.ts', 'depends-on-angular')(format=".")
:marked
**Open** `unit-tests.html`
**Find** the `<!-- #1. add the system.js library -->` comment.
**Replace** the scripts tags beneath it with the all the needed angular scripts:
+makeExample('testing/ts/unit-tests-5.html', 'import-angular')(format=".")
:marked
We should now be ready to see our 3 expectations fail when reloading our test page.
figure.image-display
img(src='/resources/images/devguide/testing-an-angular-pipe/two-failures.png' alt="2 failed tests")
:marked
## Uppercase, if you please
The first two tests that passed were our old `hero` interface tests, so it makes sense that those passed. Of our three new expectations, one still passed though.
```
MyUppercasePipe
transforms "abc" to "ABC"
transforms "abc def" to "ABC DEF"
leaves "ABC DEF" unchanged
```
Ah but of course! Our simple pipe doesn't transform the input at all, and the third test expected
input to not be changed.
All we have to do now is actually transform text to uppercase in our pipe.
+makeExample('testing/ts/app/my-uppercase.pipe.ts', 'uppercase')(format=".")
:marked
Are we done now?
figure.image-display
img(src='/resources/images/devguide/testing-an-angular-pipe/zero-failures.png' alt="0 failed tests")
:marked
The glorious green is back with us again!
We tried a bit of test driven development and it seems to have guided us to success.
But it's not always feasible. For instance, sometimes we need to write tests for existing functionality, like what we're about to do with the rest of Tour of Heroes.
If we are writing new code though, writing tests might just be what we need to help us track our progress and keep the end result in sight at all times.
:marked
## Whats Next?
Now we can test parts of our application that we *load* asynchronously with system.js.
What about testing parts that *are themselves asynchronous*?
Let's test a service with a public asynchronous method that fetches heroes from a remote server.