DEV Community

Bearded JavaScripter
Bearded JavaScripter

Posted on • Edited on

TDD in Angular - Understanding an Angular Unit Test

In my previous post, I talked about the Basics of General Testing. It was a short introduction into the different types of testing and how to approach testing in Jasmine.

In this article, I want to take a look at the auto-generated unit test files of an Angular Application and explain what's happening. I'll unit test services for my examples since it's a good place to start understanding the fundamentals. Also, Angular Components have a bit more going on under the hood and that requires an article of its own.

Many Angular developers get confused and even overwhelmed by what's happening in a .spec.ts file. This article will change that.

Code for this article can be found here.

Angular Services

Services are by far the easiest to unit test. They're simply Injectable classes that may or may not have some state and contain a collection of functions. However, they shouldn't be taken lightly. Your services are where all your business logic should be. Therefore, testing them as much as you can will prove useful.

Let's take a look at an InventoryService and its auto-generated .spec.ts file:

There's a few points to note here.

There's a describe block that groups all our tests together for this service. A variable called service is also initialized.

The beforeEach block contains code that is run before every single unit test in this spec file. It helps us have a clean slate before running each test so that previous tests don't interfere. This helps with the essence of unit testing (testing one thing in isolation without any external factors).
There are other blocks of code like this to help us maintain clean tests, namely beforeAll, afterEach and afterAll. You can read more about them and more pretty cool testing tools in the Jasmine Global API.

Looking inside the beforeEach block, we see 2 unfamiliar pieces of code. TestBed.configureTestingModule creates a dummy module for us to work with. TestBed.inject initializes our service and injects it into that dummy module. This holds true for components, pipes, guards, etc. This is the unit testing philosophy combined with Angular's architecture.

An Angular application must have at least one module so a dummy module is created with only the piece of code being tested (in this case, the service) and nothing else. This way, nothing else from the outside can interfere with the tests. Pure Isolation.

Each it block of code is a unit test. it is a function that accepts 2 arguments: A string describing the test and a function that must contain an expect assertion function. This expect function is what Jasmine runs to assert expected values against actual results.

it('should be created'), () => {...} is a unit test that is always created for any component, pipe, service, etc. It doesn't make sense looking at other unit tests if we can't initialize our code in the first place.

Building our Service

Let's say that I wanted to add some code to track the number of items in my inventory and a way to increment and decrement the amount.

Remember! We're approaching this from a TDD standpoint. We can write empty placeholders and then Tests first!

We write tests for what we want our code to do and then consider other cases. Tests for main functionality should look something like this:

You'll notice I used fdescribe as opposed to describe. This means "Focused Describe" and Jasmine will only run this suite of tests instead of all tests in the Application.

When we run our unit tests, we'll notice some failures (as expected).

Alt Text

It's telling us that the "should increment the count" and "should decrement the count" tests are failing. This is expected since we didn't write any code in there as yet. Let's change that.

And now our tests are passing:

Alt Text

The more astute among you might have realized that our decrement function isn't finished. We shouldn't be able to decrement if the inventoryCount is already 0.

A reasonable test for this can be:



it('should not decrement when count is 0', () => {
    expect(service.inventoryCount).toBe(0);
    service.decrementCount();
    expect(service.inventoryCount).toBe(0);
  });


Enter fullscreen mode Exit fullscreen mode

Re-running the tests gives us:

Alt Text

Our function currently decrements whatever value is stored in the service. We want it to decrement only when the value isn't 0. This is also a simple way to make sure that inventoryCount never falls below 0 (assuming you want your system like that).

We can modify the function to be:



 decrementCount() {
    if (this.inventoryCount === 0) return;

    this.inventoryCount--;
  }


Enter fullscreen mode Exit fullscreen mode

Now our tests are passing again.

Alt Text

Conclusion

In this article, we covered the following:

  • The basic structure of an Angular Unit Test
  • What happens in the beforeEach block
  • Why a dummy module is necessary for testing
  • Building an Angular Service using TDD

There are a lot more of these articles to come where we'll dive deeper into services, mocking, component life cycles, etc. Stay tuned for more and thanks a bunch for reading! ๐Ÿ˜„

Top comments (1)

Collapse
 
spideep profile image
Alexis Rengifo

On decrementCount() if the inventoryCount is 0 you return nothing. For which cases to throwing an error could be more convenient?