Blog

Best Practices for Angular Unit Testing

Software development and testing go hand in hand. Making sure that our code and apps behave the way they should is key, and it’s important to know that testing isn’t just a QA’s job. As developers, we can start that process from the very beginning by using unit testing, and doing so on frameworks like Angular is extremely easy. 

From the very start of an Angular project’s creation, we are given the foundation to start building unit tests. In this article, we’ll explain their importance and give some examples of ways to make the most out of this tool.

Covering the Basics of Unit Tests

Conceptually, unit tests serve to test code individually, separated in units. Instead of testing an application as a whole, we break it down into smaller units that are easier to test and to conceptualize. This allows us to check our code in a granular way and have a clear separation of concerns within each unit. The easiest way to separate these units is by individual file, be it an Angular service, component, pipe, etc.

Applications usually implement several types of testing methodologies. When writing unit tests, we don't worry about the relationship of one unit with other units–that's automated through integration tests. We also avoid mocking a browser, any user actions, or interacting with a live version of the app, as that is automated through end-to-end testing.

Benefits of Unit Testing

Unit testing can take time to set up, but it’s an investment that pays off very quickly. Whether starting from scratch or expanding on existing coverage, here are some benefits you and your team will notice as you work on unit tests:

  • It forces you to understand your code
  • It lets you think of edge cases and help you cover them
  • It serves as documentation for others and keeps track of previous behavior
  • Facilitates refactoring and changes to existing code
  • It encourage code separation and coding in a granular manner
  • Allows developers to feel more confident with their code
  • It helps you catch any potential errors before they are deployed

Setting it up

Out of the box, native support is provided as soon as you set up an Angular application. Once you generate your core app, you will find .spec.ts files at the component level. By default, Angular uses Karma to run tests and Jasmine as the testing framework, but know that these concepts can be applied to other JavaScript testing frameworks such as Jest and Mocha.

To check your tests, run ng test in your terminal. You can also check your entire application’s code coverage by running ng test --no-watch --code-coverage.

By default, Agular will create a .spec.ts file whenever you use the CLI to generate files. This is a clean way to keep track of your test files at the same level as the files they’re testing.

Best Practices

When writing code, we usually want to follow DRY (Don't Repeat Yourself) principles–but it's ok to write "damp" code in unit tests. In other words, it's ok to repeat yourself if it helps to make the unit test clearer. This is specially relevant in the Arrange stage, since several tests can require a similar starting setup.

We should always try to follow the triple A principle:

  • Arrange (setting up preconditions)
  • Act (performing actions)
  • Assert (validation of results)

Ideally, we also leave an empty line between each 'A' section. This way, it's easy to read what's being set up, what the actions are, and what the result validations are. 

Your structure should usually be:

describe("ElementName", () => {
  describe("when FunctionName is called", () => {
    it("results description when action description", () => {
      // Arrange
      // Act
      // Assert
    })
  })
})

It's ok if very simple tests don't require a separate Arrange or Act section. Example:

it("should return false when classType is null", () => {
  component.classType = null
  const result = component.isCommonClassSelected()
  expect(result).toBeFalse()
})

Make sure to use the beforeEach() function to clean up code between tests when needed.

You can declare stubs or variable mocks at the end of the file to make it clearer to read. Depending on the actions of the function in which you’ll use these stubs, you might want to call them with spread operators to avoid modifying their values before other tests are run.

It's worth mentioning that private functions are taken into account as part of the code coverage, so you should try to test them too. If you need to access a private function or variable, use the bracket notation. Example: component['functionName']()

Testing Components

This will make up the bulk of your unit testing, as most of the business logic in the front-end side is usually located within components.

Here, we are usually testing for state changes within the component itself (testing variables value changes, checking that we called expected functions) and an overview of its interaction with a service. For this last part, we might need to mock a service with the respective functions to add a spy to. We don't need to do integration tests, but check that we call the expected service function.

describe(‘when isSaveButtonDisabled is called’, () => {
    it(‘should return true when the form is invalid’, () => {
      const isAllFormValidSpy = spyOn(component, "isAllFormValid").and.returnValue(false)
      component["isLoadingResponse"] = false
      const result = component.isSaveButtonDisabled()
      expect(isAllFormValidSpy).toHaveBeenCalled()
      expect(result).toBeTrue()
    })
})

Testing Services

In services, the main concern should be to test our URLs and HTTP functions. When these functions are called, what URL are we calling, with what HTTP function, and what parameters. Here’s an example:

describe(‘when loadCompanyInformation is called’, () => {
    it(‘should load company data’, () => {
      service.loadCompanyInformation()
      const request = httpMock.expectOne(`${apiPath}`)
      expect(request.request.method).toEqual("GET")
      request.flush(companyInformationStub)
      httpMock.verify()
    })
})

Testing Pipes

Pipes are usually simple to test, as we are just testing to check if the transform function returns what we expect from it. This is a good moment to stretch your testing beyond the expected ranges of an ideal scenario to be certain of how it will behave.

Example:

describe('when transform is called', () => {
    const pipe = new PhonePipe()
    it('should format a phone number’, () => {
      const numeral = pipe.transform('+50688888888')
      expect(numeral).toEqual('+506 8888 8888')
    })
})

Testing Attribute Directives

Attribute directives can be complex due to the variety of actions these can perform. Because of this, your tests can be very specific, but the general idea is understanding how the directive affects the element and how to check that it was modified properly.

it('should color 1st <h2> background "yellow"', () => {
    const bgColor = des[0].nativeElement.style.backgroundColor
    expect(bgColor).toBe('yellow')
})

Conclusion

As applications grow, it’s critical to be able to keep track of current behavior. A solid foundation of unit tests is a great way to implement this, and teams can set their own goals of code coverage percentage to hold themselves accountable. These are particularly important for teams performing refactors, as it helps confirm that the existing behavior of the file was kept intact after code changes. These tests will also end up as a form of documentation to go along the files they’re testing, helping others understand the expected behavior of code that was previously written and tested.

Another potential benefit for teams is the ability to implement Test Driven Development (TDD), which functions by writing tests before starting any code. At first, all tests will fail, but they should start to succeed as the feature is developed. This forces developers to think about their implementation and the end result before they start coding. The other added benefit is that the unit tests are kept up to date as new features are added.

If you want to learn more about unit testing in Angular, you can read the official Angular documentation, or the official Jasmine documentation.

 

Resources:

https://angular.io/guide/testing

https://jasmine.github.io/

https://karma-runner.github.io/latest/index.html

https://jestjs.io/

https://mochajs.org/

https://testing-angular.com/

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.

TALK TO OUR SALES TEAM