This article details how to test a simple AngularJS application using unit tests and end-to-end (E2E) tests.
- Part 1 - In the first part we’ll look at unit tests, which ensure that small, isolated pieces of code (e.g., a unit) behave as expected.
- Part 2 - In part two we’ll address E2E tests, which verify that all the pieces of code (units) fit together by simulating the user experience through browser automation. (current)
Having finished up unit testing, let’s now turn our attention to e2e testing using Protractor, which is a testing framework built specifically for AngularJS apps. Essentially, it runs tests against an app in the browser via Selenium Webdriver, interacting with the app from an end user’s perspective.
Since e2e tests are much more expensive than unit tests - e.g., they generally take more time to run and are harder to write and maintain - you should almost always focus the majority of your testing efforts on unit tests. It’s good to follow the 80/20 rule - 80% of your tests are unit tests, while 20% are e2e tests. That said, this tutorial series breaks this rule since the goal is to educate. Keep this in mind as you write your own tests against your own application.
Also, make sure you test the most important aspects/functions of your application with your e2e tests. Don’t waste time on the trivial. Again, they are expensive, so make each one count.
The repo includes the following tags:
- v1 - project boilerplate
- v2 - adds testing boilerplate/configuration
- v3 - adds unit tests
- v4 - adds E2E tests
Assuming you followed the first part of this tutorial, checkout the third tag,
v3, and then run the current test suite starting with the unit tests:
1 2 3 4 5 6 7 8 9 10
For the e2e tests, you’ll need to open two new terminal windows. In the first new window, run
webdriver-manager start. In the second, navigate to your project directory and then run the app -
Finally, back in the original window, run the tests:
1 2 3 4 5 6 7 8 9 10 11 12 13
Everything look good?
Open the test spec, spec.js, within the “tests/e2e” directory. Let’s look at the first test:
1 2 3 4 5 6 7 8
Notice how we’re still using Mocha and Chai to manage/structure the test so that it simply opens
http://localhost:8888/#/one and then asserts that the text within the HTML element with an ID of
Hello, World!. Simple, right?
Let’s take a quick look at the Angular services that we’re using:
- browser - loads the page in the browser
- element - interacts with the page
- by - finds elements within the page
Finally, one important thing to note is how these tests run. Notice that there’s no callbacks and/or promises in the test. How does that work with asynchronous code? Simple: Protractor continues to check each assertion until it passes or a certain amount of time passes. There also is a promise attached to most methods that can be access using
With that, let’s write some tests on our own.
Just like in the first part, open the controller code:
1 2 3 4 5 6 7 8 9
How about the HTML?
1 2 3 4
Looking at the Angular code along with the HTML, we know that on the button click,
greeting is updated with the user supplied text from the input box. Sound right? Test this out: With the app running via Gulp, navigate to http://localhost:8888/#/one and manually test the app to ensure that the controller is working as it should.
Now since we already tested the initial state of
greeting, let’s write the test to ensure that the state updates on the button click:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
So, in both new test cases we’re targeting the input form - via the global element function - and adding text to it with the
sendKeys() method -
Hi! in the first test and no text in the second. Then after clicking the button, we’re asserting that the text contained within the HTML element with an id of “greeting” is as expected.
Run the tests. If all went well, you should see:
1 2 3 4 5 6 7 8 9 10 11 12
Did you see Chrome open in a new window and run the tests, then close itself? It’s super fast!! Want to run the tests in Firefox (or a different browser) as well? Simply update the Protractor config file, protractor.conf.js, like so:
1 2 3 4 5 6 7 8 9
Test it again. You should now see the tests run in both Chrome and Firefox simultaneously. Nice.
Finally, to simplify the code and speed up the tests (so we only search the DOM once per element), we can assign each element to a variable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Test one last time to ensure that this refactor didn’t break anything.
Again, start with the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
1 2 3 4
Then test it in the browser.
Like last time, we simply need to ensure that
total is updated appropriately when the end user submits a number in the input box and then clicks the button.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
Run the tests and you should see:
You know the drill:
- Look at the Angular and HTML code
- Manually test in the browser
- Write the e2e test to automate the manual test
Try this on your own before looking at the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Since this controller makes an external call to https://api.github.com/repositories you can either mock out (fake) this request using ngMockE2E, like we did for the unit test, or you can actually make the API call. Again, this depends on how expensive the call is and how important the functionality is to your application. In most cases, it’s better to actually make the call since e2e tests should mimic the actual end user experience as much as possible. Plus, unlike unit tests which test implementation, these tests test user behavior, across several independent units - thus, these tests should not be isolated and can rely on making actual API calls either to the back-end or externally.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Here, when the button is clicked, the API call is made and the scope is updated. We then assert that there are 101 UL tags and 105 LI tags, representing a Github username and repo returned from the API call, present on the DOM.
- Take a look at the Page Objects design pattern and refactor the tests so that they are better organized.
- Break a test, and then pause the test before the break via
- Test your own Angular app, and then add a link to the comments to get feedback.
Be sure to check the Protractor documentation for more. Thanks again for reading, and happy testing!
Interested in learning how to test an Angular + Django app? Check out Real Python for details.