Testing Angular with Jasmine and Karma (Part 1)

In this tutorial we will be building and testing an employee directory for a fictional company. This directory will have a view to show all of our users along with another view to serve as a profile page for individual users. Within this part of the tutorial we'll focus on building the service and its tests that will be used for these users.

The primary focus for this tutorial is testing so my assumption is that you're comfortable working with TypeScript and Angular applications. As a result of this I won't be taking the time to explain what a service is and how it's used. Instead, I'll provide you with code as we work through our tests.

From personal experience, tests are the best way to prevent software defects. I've been on many teams in the past where a small piece of code is updated and the developer manually opens their browser or Postman to verify that it still works. This approach (manual QA) is begging for a disaster.

Tests are the best way to prevent software defects.

As features and codebases grow, manual QA becomes more expensive, time consuming, and error prone. If a feature or function is removed does every developer remember all of its potential side-effects? Are all developers manually testing in the same way? Probably not.

The reason we test our code is to verify that it behaves as we expect it to. As a result of this process you'll find you have better feature documentation for yourself and other developers as well as a design aid for your APIs.

Karma is a direct product of the AngularJS team from struggling to test their own framework features with existing tools. As a result of this, they made Karma and have transitioned it to Angular as the default test runner for applications created with the Angular CLI.

In addition to playing nicely with Angular, it also provides flexibility for you to tailor Karma to your workflow. This includes the option to test your code on various browsers and devices such as phones, tablets, and even a PS3 like the YouTube team.

Karma also provides you options to replace Jasmine with other testing frameworks such as Mocha and QUnit or integrate with various continuous integration services like Jenkins, TravisCI, or CircleCI.

Unless you add some additional configuration your typical interaction with Karma will be to run ng test in a terminal window.

Jasmine is a behavior-driven development framework for testing JavaScript code that plays very well with Karma. Similar to Karma, it’s also the recommended testing framework within the Angular documentation as it's setup for you with the Angular CLI. Jasmine is also dependency free and doesn’t require a DOM.

Free Node eBook

Build your first Node apps and learn server-side JavaScript.

📧

Nice!

Check your email to confirm your subscription.

As far as features go, I love that Jasmine has almost everything I need for testing built into it. The most notable example would be spies. A spy allows us to “spy” on a function and track attributes about it such as whether or not it was called, how many times it was called, and with which arguments it was called. With a framework like Mocha, spies are not built-in and would require pairing it with a separate library like Sinon.js.

The good news is that the switching costs between testing frameworks is relatively low with differences in syntax as small as Jasmine's toEqual() and Mocha's to.equal().

Imagine you had an alien servant named Adder who follows you everywhere you go. Other than being a cute alien companion Adder can really only do one thing, add two numbers together.

To verify Adder's ability to add two numbers we could generate a set of test cases to see if Adder provides us the correct answer.

Within Jasmine, this would begin with what's referred to as a "suite" which groups a related set of tests by calling the function describe.

// A Jasmine suitedescribe('Adder',()=>{});

From here we could provide Adder with a set of test cases such as two positive numbers (2, 4), a positive number and a zero (3, 0), a positive number and a negative number (5, -2), and so on.

Within Jasmine, these are referred to as "specs" which we create by calling the function it, passing it a string to describe the functionality that's being tested.

describe('Adder',()=>{// A jasmine specit('should be able to add two whole numbers',()=>{expect(Adder.add(2,2)).toEqual(4);});it('should be able to add a whole number and a negative number',()=>{expect(Adder.add(2,-1)).toEqual(1);});it('should be able to add a whole number and a zero',()=>{expect(Adder.add(2,0)).toEqual(2);});});

Within each spec we call expect and provide it what is referred to as an "actual"—the call site of our actual code. After the expectation, or expect, is the chained "matcher" function, such as toEqual, which the testing developer provides with the expected output of the code being tested.

There are many other matchers available to us other than toEqual. You can see a full list within Jasmine's documentation.

Our tests aren't concerned with how Adder arrives at the answer. We only care about the answer Adder provides us. For all we know, this may be Adder's implementation of add.

In other words, we only care that Adder behaves as expected—we have no concern for Adder's implementation.

This is what makes a practice like test-driven development (TDD) so powerful. You can first write a test for a function and its expected behavior and get it to pass. Then, once it's passing, you can refactor your function to a different implementation and if the test is still passing, you know your function is still behaving as specified within your tests even with a different implementation. Adder's add function would be a good example!

In the code above, TestBed.configureTestingModule({}) sets up the service we want to test with UsersService set in providers. We then inject the service into our test suite using TestBed.get() with the service we want to test as the argument. We set the return value to our local usersService variable which will allow us to interact with this service within our tests just as we would within a component.

Now that our test setup is restructured, we can add a test for an all method which will return a collection of users.

Here we add a test for the expectation that all will return a collection of users. We declare a userResponse variable set to a mocked response of our service method. Then we use the spyOn() method to spy on usersService.all and chain .returnValue() to return our mocked userResponse variable wrapping it with of() to return this value as an observable.

With our spy set, we call our service method as we would within a component, subscribe to the observable, and set its return value to response.

Finally, we add our expectation that response will be set to the return value of the service call, userResponse.

At this point many people ask, "Why are we mocking the response?" Why did we provide our test a return value userResponse that we created ourselves, to manually set what’s being returned from our service? Shouldn’t the service call return the real response from the service, whether it's a hard-coded set of users or a response from an HTTP request?

This is a perfectly reasonable question to ask and one that can be hard to wrap your head around when you first begin testing. I find this concept is easiest to illustrate with a real world example.

Imagine you own a restaurant and it’s the night before opening day. You gather everyone you’ve hired for a “test run” of the restaurant. You invite a few friends to come in and pretend they’re customers who will sit down and order a meal.

No dishes will actually be served in your test run. You’ve already worked with your cooks and are satisfied they can make the dishes correctly. In this test run you want to test the transition from the customer ordering their dish, to the waiter sending that to the kitchen, and then the waiters fulfilling the kitchen’s response to the customer. This response from the kitchen may be one of a few options.

The meal is ready.

The meal is delayed.

The meal cannot be made. We ran out of ingredients for the dish.

If the meal is ready, the waiter delivers the meal to the customer. However, in the event that a meal is late or cannot be made, the waiter will have to go back to the customer, apologize, and potentially ask for a second dish.

In our test run, it wouldn’t make sense to actually create the meals when we want to test the front-end’s (waiter’s) ability to fulfill the requests received from the backend (kitchen). More importantly, if we wanted to test our waiters could actually apologize to customers in the cases where a meal is delayed or cannot be made we would literally be waiting until our cooks were too slow or we ran out of ingredients before our tests for those cases could be confirmed. For this reason, we would “mock” the backend (kitchen) and give the waiters whatever scenario it is that we want to test.

Similarly in code, we don’t actually hit the API when we’re testing various scenarios. We mock the response we may expect to receive and verify that our application can handle that response accordingly. Just like our kitchen example, if we were testing our application’s ability to handle an API call that failed we would literally have to wait for our API to fail to verify it could handle that case—a scenario that hopefully won’t be happening that often!

Here we add a test for the expectation that findOne will return a single user. We declare a userResponse variable set to a mocked response of our service method, a single object from the collection of users.

We then create a spy for usersService.findOne and return our mocked userResponse variable. With our spy set, we call our service method and set its return value to response.

Finally, we add our assertion that response will be set to the return value of the service call, userResponse.

To get this test to pass, we can add the following implementation to users.service.ts.

At this point I hope testing, both its benefits and the reason for writing them, is starting to become a bit more clear. Personally, I pushed off testing for the longest time and my reasons were primarily because I didn't understand the why behind them and resources for testing were limited.

What we've created in this tutorial isn't the most visually impressive application but it's a step in the right direction.

In the next tutorial, we'll create the user profile page and a service to retrieve a Pokemon image using the Pokeapi. We'll learn how to test service methods that make HTTP requests and how to test components.

Our Goal In this tutorial we will be creating and testing the user profile page for the employee directory we started building in Part 1 of this tutorial. The user profile page will show the details...