CHAPTER 6
For the chapters on unit testing and end-to-end testing, we are going to use a sample Angular application called Online Bank. This application acts as an online ATM. It is meant to provide a simple service and a couple components so we can see how testing is used in an actual example program.
This is a sample screen from our test application. It will not win any UX awards, but we can use it and the source code to create unit and E2E tests. The initial screen simply prompts the user to enter a PIN. The menu options are not visible until the user enters their correct PIN.

Figure 11: Main ATM screen
Once the user enters their PIN, the application will show the menu options and the user’s name and balance, as shown in Figure 12.

Figure 12: Main menu
The application consists of a component, a service, and routing module, as well as three component modules:
App.module and app-routing.module will not need to be tested; they are setup work for the Angular application. App.component is our main screen component, and has no actual component code, just some HTML template code. There is also the menu code, which is a series of router links.
The customer-info service code module is a simple service that contains methods to get customer information and to update the balance and the PIN. The primary method GetCustomerDetails() gets parameters of the card number and a PIN value. In the actual application, the card number would be read by the machine when the user inserts the card into the ATM. They would then key in the PIN for two-factor authentication (something we have and know).
Code Listing 12 shows the simple customer info service. Typically, this would require an Ajax call to some web service to provide the requested information.
Code Listing 12: Customer-info service
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class CustomerInfoService { private Customers = [ { id: 1, cardNo: '1234', name: 'Blaire', balance: 1200, pin: '0825' }, { id: 2, cardNo: '5678', name: 'John', balance: 600, pin: '1224' }, { id: 3, cardNo: '8888', name: 'Kaia', balance: 2500, pin: '0322' }, { id: 4, cardNo: '9999', name: 'Jonathan', balance: 500, pin: '0604' }, { id: 5, cardNo: '0000', name: 'Kellie', balance: 4500, pin: '0304' } ]; constructor() { } public GetCustomerDetails(cardNumber: string, pinNumber: string) { // Typically, an Ajax call to get information about customer const x = this.Customers.findIndex((x) => x.cardNo === cardNumber && x.pin === pinNumber); let details; if (x >= 0) { details = this.Customers[x]; } return details; } public UpdateBalance(id: number, amt: number) { let newBalance = NaN; if (id >= 0 && id < this.Customers.length) { this.Customers[id].balance += amt; newBalance = this.Customers[id].balance; } return newBalance; } |
The method returns either an undefined value, or a simple object with customer information if a valid card number and PIN are provided. A unit test for this service would require at least two tests: one to confirm a valid object with a good card and PIN, and a second to test for an undefined object if invalid credentials are supplied.
Our three component modules (deposit, transfer, and withdrawal) are the other primary targets of our unit testing code. For example, the following two methods in the withdrawal.component are the focus of our unit test for that component. Code Listing 13 shows these two methods.
Code Listing 13: Withdrawal methods
public ShouldCashBeDisbursed(withdrawal: number) { const shouldAllow = this.balance - withdrawal > 0; if (shouldAllow) { this.balance = this.balance - withdrawal; this.service.UpdateBalance(withdrawal * -1); } return (shouldAllow); } public AssessLowBalanceFee(minBalance: number) { if (this.balance < minBalance) { this.balance = this.balance - 25; this.service.UpdateBalance(-25); } }; |
You will see that the test specification files (in the next chapter) contain test cases. Before reading further, think of the scenarios you should test for those method calls.
One of the key things to consider when designing unit tests is what we want to learn from the tests. If we look at our main app component, it is a basic, empty component with an associated HTML file. The component has no methods, so we could write some unit tests for the generated webpage. For example, here are a few tests we could write:
These tests will simply confirm that the generated webpage contains the expected text. If multiple developers can edit the source file, and there is no code review, these tests could be useful to detect if a change was made to the source code. However, you need to determine if your development environment needs such testing. In general, you shouldn’t need unit tests for static HTML pages. Version control systems and reviewed check-in policies are better solutions for detecting source code changes.
Note: In our example menu, we’ve hard-coded the maximum limit as a string. If the rules change and more cash can be withdrawn, this could trigger an error during an E2E test (if the business analysts updated the specs to reflect the new limit).
If your component generates HTML, possibly from an Ajax call or other manipulation, then testing the generated webpage makes sense in a unit test. It confirms that the actual code to generate the page performs as expected. In our code, once the user enters a valid PIN, we’d expect their name to appear in the banner. A test to confirm that the banner has a non-empty string after successful PIN entry is the type of behavior we should look for in our unit tests.
The component methods should be the primary target of your unit tests. Look at the parameters to the method and try to create unit tests with both expected good values and problem values (for example, what would happen if I were to pass a negative number to my withdrawal method). Don’t design tests with only good input; instead, try deliberately bad input. The mere thought of designing a test with bad input can often make your code better.
Looking at the ShouldCashBeDisbursed() method, what should be returned if I pass a negative number? Assuming the balance is positive, the method will always return true and will increase the balance. With this knowledge obtained by thinking about the unit test, we might decide that a Boolean return value is not the best, perhaps a status code or triggering an exception would be better. By thinking about your tests, you will find yourself writing more robust code.
Note: TDD (test-driven development) is exactly about this—write your tests first, and they will all fail (since there’s no code yet). As you implement your method and the tests begin to pass, you can be more confident in your method implementation than if you code first, test later.
Our application is a simple example of an Angular application, so we can focus on how to create unit and end-to-end tests. You can find the source code here (with complete testing files).