- Create unit tests in Angular.
As we learned in other sections of this course, testing can take multiple forms, with unit testing and end-to-end testing being 2 very specific types of tests:
- With unit testing, we are focused on testing the functionality of a specific component
- With end-to-end testing, we are focused on testing how the application works from the perspective of a user
Angular provides tools for unit testing out of the box, and has even created a few test files for us while we were creating our components.
Jasmine is a testing framework that lets us define test suites and create specific tests within those test suites. It also gives us tools for setting up Angular-specific ways to test components, services, etc.. without having to run our full application in a browser.
Karma is a test running that lets us run multiple tests and create reports in a browser-readable format to make it easier to interpret the test results.
Let's start by cleaning up the tests that the Angular CLI created automatically for us when we created all our application components. We will then be adding some of these tests back as we learn about unit testing.
Each component has a <component-name>.component.spec.ts
test file associated
with it, so to get rid of all the tests that were created by default, remove all
the spec.ts
files from your project:
app.component.spec.ts
application-component.component.spec.ts
contact-list-component.component.spec.ts
conversation-control-component.component.spec.ts
conversation-history-component.component.spec.ts
conversation-thread-component.component.spec.ts
sender-message-component.component.spec.ts
send-message-component.component.spec.ts
header-component.component.spec.ts
Now let's create a test file for a component we actually want to test. Let's start with the header component.
The functionality Jasmine (the testing framework that comes with Angular) provides is made available through specific functions. The main functions are:
describe()
: this function denotes an entire test suite and has the following structure:- The first argument is the description of the test suite in plain words
- The second argument is a function that will get called when the test suite is run
- Here is an example test suite:
describe("'describe()' is a Jasmine function that lets us define a test suite", () => {
// test suite code here
});
});
it()
: this function denotes a specific test case in a test suite, and has the following structure:- The first argument is the description of the test case in plain words
- The second argument is a function that will get called when the test is run, which is every time the parent test suite is run
- Here is an example test case within our test suite defined above:
describe("'describe()' is a Jasmine function that lets us define a test suite", () => {
it("'it()' is a Jasmine function that lets us define a test case", () => {
expect(true).toBe(true);
});
});
Let's save this code in header-component.component.spect.ts
in the
header-component
folder of our header component.
Now you can run this test with the following command from the root directory of your project:
ng test
This commands use Karma to run your Jasmine tests. We'll explain more about
Karma and why it's needed below. For now, just know that ng test
runs your
Jasmine tests using Karma and Karma will by default open a browser window to
show you the results of running your tests, so the command above will result in
a window like this being shown to you:
As you can see, our test suite was run, and within our test suite, our test was run and successful.
Let's now create a new suite within the same suite and make it fail to see what that output looks like. This is what the new test suite looks like once you've added the new test:
describe("'describe()' is a Jasmine function that lets us define a test suite", () => {
it("'it()' is a Jasmine function that lets us define a test case", () => {
expect(true).toBe(true);
});
it("should fail in order to demonstrate what a test failure looks like in Karma", () => {
expect(false).toBe(true);
});
});
And now the Karma output shows the failure:
A couple of things to note:
- The second test has a more standard description that starts with "should" and describe the expected result of that specific test
- When you run
ng test
, it starts in "watch mode", which means that your test will run automatically as soon as they are updated. If you keep the browser window that it first opens opened, then that will also refresh automatically when you updated any of your test files. - You can now see that the Karma report shows you 1 passing test and 1 failing test and shows details of the failing test
We will not be getting into the details of Karma for this course, but we do want to give an overview of why Angular uses it and how Karma is different from Jasmine:
- Jasmine is the testing framework and gives us the tools we need for actually writing our unit tests, including tools for specifying a test suite, tests within a suite and assertions within a test. It also provides us tools for integrating with the Angular framework (as we will see in the section below).
- Karma is a tool for running our tests in specific browsers and emulate specific hardware. The tests we write in Jasmine should be the same across all platforms and devices, but the results may not the same in all the combinations of those environments.
As we saw in the previous section, Karma also has convenient reporting and watch functionality that makes it easier to get a tight feedback loop between code updates and test results.
Before we start extending our test such that it can test our header component,
we need to get familiar with a new Jasmine function: beforeEach()
.
beforeEach()
takes a function as an argument and that code is run for every
test within the test suite (i.e. the describe()
) where the beforeEach()
is
defined. This allows us to have some common setup code for all the tests, rather
than having to duplicate that code in all tests.
Let's add a beforeEach()
to our existing suite:
describe("'describe()' is a Jasmine function that lets us define a test suite", () => {
let testRunCount: number = 0;
beforeEach(() => {
testRunCount++;
console.log("'beforeEach()' has run " + testRunCount + " time(s)");
});
it("'it()' is a Jasmine function that lets us define a test case", () => {
expect(true).toBe(true);
});
it("should fail in order to demonstrate what a test failure looks like in Karma", () => {
expect(false).toBe(true);
});
});
Let's highlight a few things about this updated code:
- The
beforeEach()
function is at the same level inside thedescribe()
function as each one of the tests - We created a
testRunCount
variable that is global to the test suite - We increase
testRunCount
by 1 every timebeforeEach()
is run so that we can track the number of times it's called by Jasmine - In
beforeEach()
weconsole.log()
the number of times it's been run every time it runs - You can view the output of
console.log()
in the console of the browser window Karma opens
You should now see an output like this from Karma and on the console: