Intro to Unit Testing In Angular With Karma & Jasmine

Ah, unit testing. One of my favorite subjects. I’ve been big on unit tests for what seems like more than a decade now, but I’ve never dipped my toes into the UI unit test pool! It’s been long enough, though. It’s time to learn!

So, I’m taking my first look at writing tests for an Angular application using Karma and Jasmine. I thought this tutorial at scotch.io was a great first read. It provides overviews of what Karma and Jasmine are, and walks you through the steps of creating an Anuglar application and adding tests from scratch using the Angular CLI, and that’s basically the foundation I’m working with today.

In this article, I’m going to write some tests as I extend behavior on “an existing product” and also write tests for some untested code. I’ll be using my favorite boilerplate code–the .NET Core CLI Angular template–as my existing product, as it provides a functional appliction with some existing tests for us to build on.

If you haven’t already, start by creating a new Angular application by running the following command:

dotnet new angular -o my-app

Verify that can run your new application by running dotnet run in the my-app directory. my-app/ClientApp contains the Angular application. Since we’re going to be adding to existing test suite, we should verify that the existing tests run and pass. Run the following from my-app/ClientApp:

npm install
ng test

You should see output like the following, indicating that all tests have passed.

Now that we’ve verified that existing tests work, we can start making changes. Let’s assume we want to modify the Counter page to have a decrement button in addition to its increment button, because sometimes we click too many times. Before we start making changes to the Counter component itself, we can describe the desired behavior in the existing counter.component.spec.ts file, which already contains similar logic for the increment functionality. Most of what needs to be written can be copied and adjusted from the Increment test.

Here’s the test I wrote:

it ('should decrease the current count by 1 when Decrement is clicked', async(() => {
  const countElement = fixture.nativeElement.querySelector('strong');
  expect(countElement.textContent).toEqual('0');

  const decrementButton = fixture.nativeElement.querySelector('#decrement-button');
  decrementButton.click();
  fixture.detectChanges();
  expect(countElement.textContent).toEqual('-1');
}));

I can run tests again, and guess what–it’ll fail. Cool. So let’s make it work, which is just two steps. First we need to add the button to counter.component.html:

<h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>

<button class="btn btn-secondary" (click)="decrementCounter()" id="decrement-button">Decrement</button>

And then we need to add the logic for when it’s clicked in counter.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter-component',
  templateUrl: './counter.component.html'
})
export class CounterComponent {
  public currentCount = 0;

  public incrementCounter() {
    this.currentCount++;
  }

  public decrementCounter() {
    this.currentCount--;
  }
}

That’s it. Now we can run ng test again and see that we now have 3 passing tests.

Good stuff. Now, let’s turn our attention to some untested code. FetchDataComponent is functional but has no tests for its display of data retrieved from the API. We’ll need to mock the API call to return some data and then assert that results are displayed as expected.

Here’s my fetch-data.component.spec.ts file with a test that mocks the API call and asserts that data is populated in the table on the UI:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { FetchDataComponent } from './fetch-data.component';

describe('FetchDataComponent', () => {
    let component: FetchDataComponent;
    let fixture: ComponentFixture<FetchDataComponent>;
    let httpMock: HttpTestingController;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            declarations: [FetchDataComponent],
            providers: [{ provide: 'BASE_URL', useValue: 'https://my.fake.url/' }]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(FetchDataComponent);
        component = fixture.componentInstance;
        httpMock = TestBed.get(HttpTestingController);
    });

    it('should retrieve weather forecasts', async(() => {
        const dummyForecasts = [
            {
                date: "date1",
                temperatureC: 0,
                temperatureF: 32,
                summary: "summary1"
            },
            {
                date: "date2",
                temperatureC: 100,
                temperatureF: 212,
                summary: "summary2"
            }
        ];

        const req = httpMock.expectOne('https://my.fake.url/weatherforecast');
        req.flush(dummyForecasts);

        expect(component.forecasts).toBe(dummyForecasts);
        fixture.detectChanges();
        const rowCount = fixture.nativeElement.querySelectorAll('table tr').length;
        expect(rowCount).toBe(3); // header plus two data rows
    }));
});

There’s a lot to unpack with this test, and I’m going to attempt to give a visual, summary-level explanation in the screenshot below.

Once again, we run tests using ng test and see that we now have passing tests for FetchDataComponent!

Output Parameters in Argument Constraints with Rhino Mocks… Again!

One of my favorite things about being a blog owner and operator is when I’m looking for something and find that I’ve already answered my own question. This just happened to me as I was trying to re-figure out how to use output parameters with argument constraints with Rhino Mocks.

I found an article on this very topic written by me (thanks, self!), but as I was reading I noticed that I left something important out. So here’s a quick, mini do-over!

Let’s say we need to stub function Bar as it’s defined in the following interface:

public interface IFoo
{
    bool Bar(out string outty);
}

To use an argument constraint with the output argument, I need to do two things. First, I need to use Arg<T>.Out. The second thing–the thing that I failed to mention in my previous post–is that I need to use the Dummy property to make it compile.

Here’s what an example might look like. I stubbed the Bar function to return true and assign the value fake value! to the output argument.

IFoo mockFoo = MockRepository.GenerateMock<IFoo>();
mockFoo(x => x.Bar(out Arg<string>.Out("fake value!").Dummy)).Return(true);

Not too shabby!

Several Patterns for Refactoring Static Classes for Testability

Static classes and methods can be tricky business for unit tests, and I’ve had to do a lot of refactoring them lately to get some tests going. Here are a few different ways to make your static classes test-friendly.

For the examples below, assume we need to refactor the following class to make it more testable:

public static class Foo
{
    public static void Bar()
    {
        // do something
    }
}

Convert to Instance Class

A simple and straightforward solution is to convert your static class to an instance class. If you need to prevent multiple instances from existing, you can implement the singleton pattern in your class. It’s easy to do this, but it can be risky or tedious to implement across a solution depending on how much your static class is being used. For example, if your class is being accessed hundreds or thousands of times across many different projects, you may not want to do this because you’ll have to update each and every caller.

public class Foo : IFoo
{
    private static IFoo Instance { get; set; }
    
    static Foo()
    {
        Instance = new Foo();
    }
    
    private Foo()
    {
    }
    
    public void Bar()
    {
        // do something
    }
}

Keep Static Class, Extract Implementation #1

If you want or need to keep your static classes/methods as static without having to change every caller, you can extract the implementation of your static class to a new instance class and adjust the static class to use that. The implementation class uses the singleton pattern to expose a single static instance that will be used by the static class.

public static class Foo
{
    public static void Bar()
    {
        FooImplementation.Instance.Bar();
    }
}

internal class FooImplementation : IFoo
{
    public static IFoo Instance { get; private set; }
    
    static FooImplementation()
    {
        Instance = new FooImplementation();
    }
    
    private FooImplementation()
    {
    }
    
    public void Bar()
    {
        // do something
    }
}

public interface IFoo
{
    void Bar();
}

This works great, but it does require unit test authors to have knowledge of the new implementation class. This strikes me as slightly non-intuitive for developers since you go to class B to mock the behavior of class A. And that brings us to our next option…

Keep Static Class, Extract Implementation #2

Another option is to tweak the above strategy slightly so that the static property is added to the static class instead of on the implementation instance class. The nice thing about this approach is that you don’t need to have any awareness of the default implementation when providing the static class with an alternate implementation.

public static class Foo
{
    private static IFoo Implementation { get; set; }
    
    static Foo()
    {
        Implementation = new FooImplementation();
    }
    
    public static void Bar()
    {
        Implementation.Bar();
    }
}

internal class FooImplementation : IFoo
{
    public void Bar()
    {
        // do something
    }
}

public interface IFoo
{
    void Bar();
}

Create a Wrapper

Another option is to create a wrapper. Much like the first option presented, this approach has the downside of needing to update all callers to use the wrapper. However, if you are unable to modify the contents of your static class, this may be your best bet.

public static class Foo
{
    public staic void Bar()
    {
        // do something
    }
}

public class FooWrapper : IFoo
{
    private static IFoo Instance { get; set; }
    
    static FooWrapper()
    {
        Instance = new FooWrapper();
    }
    
    private FooWrapper()
    {
    }
    
    public void Bar()
    {
        Foo.Bar();
    }
}

interface IFoo
{
    void Bar();
}

Unit Test Sending Email with SmtpClient

I have a workflow activity that sends email (the code for this activity can be found here), and I wanted to write integration tests using SpecFlow. This creates an interesting problem. I don’t want to simply mock everything out, but I also don’t want to require a valid SMTP server and email addresses. I also want the test to pass or fail without having to check an email inbox.

Luckily, there are configuration options used by the SmtpClient class that can be used to create files when email messages are sent. This is accomplished by adding some simple code to your application configuration file. (Source here.)

<system.net>
    <mailSettings>
        <smtp deliveryMethod="SpecifiedPickupDirectory">
            <specifiedPickupDirectory pickupDirectoryLocation="C:\TempMail" />
        </smtp>
    </mailSettings>
</system.net>

This solution is easy and it works, but it creates another problem: I want my test to run automatically on other machines. I don’t want to hardcode a path into the config file because I could run into problems with user permissions or directory structure. I found this blog post that demonstrates how to change the directory programmatically. The only thing I didn’t like about that solution is that it requires the app.config change shown above. I modified the posted solution slightly so that the configuration file section is not needed. Here’s the result:

var path = GetTempPath();

// get mail configuration
var bindingFlags = BindingFlags.Static | BindingFlags.NonPublic;
var propertyInfo = typeof(SmtpClient)
    .GetProperty("MailConfiguration", bindingFlags);
var mailConfiguration = propertyInfo.GetValue(null, null);

// update smtp delivery method
bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
propertyInfo = mailConfiguration.GetType()
    .GetProperty("Smtp", bindingFlags);
var smtp = propertyInfo.GetValue(mailConfiguration, null);
var fieldInfo = smtp.GetType()
    .GetField("deliveryMethod", bindingFlags);
fieldInfo.SetValue(smtp, SmtpDeliveryMethod.SpecifiedPickupDirectory);

// update pickup directory
propertyInfo = smtp.GetType()
    .GetProperty("SpecifiedPickupDirectory", bindingFlags);
var specifiedPickupDirectory = propertyInfo.GetValue(smtp, null);
fieldInfo = specifiedPickupDirectory.GetType()
    .GetField("pickupDirectoryLocation", bindingFlags);
fieldInfo.SetValue(specifiedPickupDirectory, path);

Using this code, I’m able to change the email delivery method and specify the output path programmatically. In my SpecFlow test, I create a temporary directory, process and verify email files created by my workflow, and cleanup. It works like a charm!

Testing Code Paths vs. Testing Behavior

I have a colleague that’s my equal in terms of unit testing enthusiasm, but we have very different philosophies. He tends to write methods first, then test the hell out of them to ensure that all code paths have been covered and that there are no holes. I tend to code using more of a TDD workflow, writing tests for each behavior that I expect from a method and not worrying about anything else that may or may not being going on.

Both approaches are valid. As we code, we both think about things that could go wrong with our code, and we both account for those things and make sure they’re tested. At the end of the day, we both end up with relatively bug free solutions that work well. Both methods produce high levels of code coverage. although focusing test writing on code paths will likely result is slightly higher coverage since the tests.

Yes, there’s a lot that’s similar about these two different approaches, but the differences are very important. The TDD mantra is “red, green, refactor.” The idea is that you write a failing test, add code to make the test pass, and then refactor the solution to clean up and optimize. This workflow is made for behavior-based testing. You expect a certain result from the method being tested. Once it’s producing that result, it shouldn’t stop producing it due to refactoring or optimizations.

The same statement can be made for tests written based on code paths: an expected result should continue to be produced after code is optimized. I’m venturing to say that optimizations are less likely to occur with the code-first approach, though. When you write code first, you don’t write tests until you’re done. And, since you’re writing tests based on the “finished” code, it’s less likely that you’ll discover flaws. Refactoring also seems less likely for the same reason. If refactoring does occur–which it should–then there’s a different problem: code paths that were once different may now be the same. You may have unknowingly written duplicate tests! (That’s not to say that the duplicate or redundant tests are bad, but you’ll have spent time writing code that, in the end, didn’t need to be written.)

Every developer I’ve ever met has learned to code before they’ve learned to write unit tests. Unit tests are generally written in code, so it’s hard to imagine learning them in any other order. Because we learn these two things in that order, we generally learn to write unit tests by following code paths. If you’re one of those code-first-and-write-tests-later types, I urge you to step out of your comfort zone and start writing behavior-based tests FIRST. You’ll code with purpose and write meaningful tests. You’ll be able to refactor with confidence, knowing that your code’s behavior has been unaffected by your chances. Like any skill, it takes some time to get used to, but I strongly believe you’ll produce higher quality code more efficiently once you become proficient.

Write Tests First–But Not ALL Tests First

I’ve been preaching hard about test-driven development and the importance of writing tests first. I can feel the culture beginning to shift as people are slowly starting to buy in, but I had an interesting discovery yesterday.

I was invited to a meeting by some developers that wanted me to walk through how I would’ve used test-driven development to write tests and develop a new project that they had recently started. It was essentially a data validation project that retrieved data from the database, checked it against a set of rules, and recorded any violations. We were reviewing a Controller class that was responsible for orchestrating the operation.

“Okay,” I said, “What is this thing supposed to do?”

The developers told me it retrieves records, validates each record, and saves the validation results. So, without knowing anything more than that, I figured there were at least two external dependencies: an IDataAccess responsible for retrieving records and saving the result and an IValidator that does the data validation/rule-checking. I drew a diagram on the whiteboard to show the relationships between the Controller and these two components.

I explained that since we know the dependencies and how we expect them to be used, we can begin to write tests. We also need to know how our application should react when the dependencies are missing. I started to rattle off some tests:

  • ProcessRecords_NullDataAccess_ThrowsArgumentNullException
  • ProcessRecords_NullValidator_ThrowsArgumentNullException
  • ProcessRecords_DataAccessReturnsNonEmptyList_ValidatesEachRecord
  • Etc.

The group was with me, but they quickly shifted focus to what tests were needed for the DataAccess class. And the tests for its dependencies. And everything else.

“Whoa, whoa, WHOA. None of that matters for this. All we care about is this method,” I say.

“Well, yes, but we want to do test-driven development. We thought the goal was to have all of our tests written first so we can go back and implement them.”

That’s when I had my epiphany. When I’m telling people to write tests first, they think I mean write ALL tests first. This is not the case! It would be virtually impossible to think about every code decision and execution path for an entire method/class/application upfront, and I think that’s where there’s been a disconnect. I can look at the finished code and come up with all the tests, but there is no way I could’ve come up with every single test for every single method before ever writing any code.

I went to another small team of developers and asked them if they also thought I meant “all tests first.” They did. It’s disappointing to know that I was sending the wrong message, but I’m glad I have something to address that will hopefully result in more passengers on the TDD train.

When you’re getting started with test-driven development, don’t try to write every single test first. Don’t even try to write as many tests as you can think of. You just want to write tests as you go. What does this method need to do next? Write a failing test, then write the implementation to make it pass. Red, green, refactor, baby! I’m also exchanging my “tests first” mantra for a new one: “test as you go!”

Don’t Test Your Own Work?

I’ve been reading several discussions and articles on the topic of whether or not developers should test their own code, and I’m finding that the general consensus is, “No.” (Wait, it’s too early to stop reading! You must test!)

When talking about testing in this context, I’m referring to functional testing—not unit testing. There is absolute agreement in the development community that developers should write unit tests for the code they produce, and there is general agreement that functional testing by the authoring developer provides value, too. The argument that most of these discussions make against developers testing their own code is that the developer shouldn’t be responsible for putting the final stamp of approval on their output before it’s delivered to customers. This is very much in-line with my personal belief that one-developer projects are destined for failure, a problem that has been particularly prevalent on my development team.

I thought this was a decent analogy (taken from here):

Writing code is a bit like map-making.

You point your developers in the direction of some uncharted wasteland, supply them with coffee, and let them go hacking through the undergrowth. If you’re lucky, they’ll pop back up in a few weeks time, and they’ll have brought geometric exactitude to Terra Incognita. If you’re unlucky, they’ll go native and it’ll end up with heads-on-spikes and tears-before-bedtime.

Anyway: you want to test your new maps. The WORST people to test the maps are the people who wrote them. They know, without thinking about it, that you have to veer West when you reach The Swamp of Unicode Misery or you’ll drown. They know that the distances are slightly unreliable because they contracted malaria near the Caves of Ui and the resulting fever made the cartography kinda hazy.

In other words, when you develop a solution, you know how it was written to work, and you’ll have a tendency to test it that way. You know what the configuration settings are supposed to be, and you know when buttons are supposed to be pressed and when knobs should be turned. You are the one person in the entire universe for which your solution is most likely to work.

So that’s all good and well, but what can you do about it? It’s simple: get somebody else involved. Have a peer test your solution or demo the solution to others. These are great ways to find functional problems that you may not have considered. None of us is able to produce a perfect solution 100% of the time. It’s impossible to not make assumptions and not make mistakes, but getting others involved is a terrific way to overcome quality issues and oversights that can arise from those assumptions and mistakes.

Please feel free to comment on the subject. I’d love to hear what you think about this, particularly if you disagree.