Check out our Angular Book Series.

My Unit Tests are Running out of Order with Angular 8!

This is a weird one. I'm updating Unit Test from my LearnWith series for Angular 8. And I ran into a few tests that would inconsistently pass.

The app behind the LearnWith series is a task manager, and one of the tests that was failing was to mark a task completed.

Let's look at the test:


describe('completeTask() ', function () {
let task : TaskVO = {taskCategoryID:1,taskID:10,completed:true} as TaskVO;
};

This is a describe() block, for the completeTask() method and it also creates a default task.

I had two tests in this block. The first makes sure the task was completed:


it('Make task Completed', () =>
{
taskService.completeTask(task).subscribe(
value => {
expect(value.resultObject[0].completed).toBeTruthy();
});
const req = httpMock.expectOne(url) ;
req.flush({"resultObject":[task],"error":0});
}

I truncted it for simplicity. But, it takes the full task--defined as a constant in the describe block--passes that task into a service, creates a request, resolves it, and checks the value returned.

The same test suite had a test to mark a task not completE:


it('Make task Not Completed', () =>
{
task.completed = false;
taskService.completeTask(task).subscribe(
value => {
expect(value.resultObject[0].completed).toBeFalsy();
});
const req = httpMock.expectOne(url) ;
req.flush({"resultObject":[task],"error":0});
}

The second test sets the completed value on the task object to false; then runs very much the same code, and after the service is resolved; makes sure that the completed value is false.

The second test always ran without problems, but the first test would sporadically fail. My only explanation for this is that the tests must not be run in the same order on every execution of the test suite.

The task object is not reinitialized after every it() test, therefore the values can stay constant across multiple tests.

There are two solutions to this. The first is to make sure the first test sets the default value:


it('Make task Completed', () =>
{
task.completed = true;
// rest of test

That magically worked it.

The second solution is to move the task initialization into a beforeEach() block so it would be re-initialized before each test. That would look like this:


let task : TaskVO;

beforeEach(() =>
{
task = {taskCategoryID:1,taskID:10,completed:true} as TaskVO;
});

Either solution would solve the problem; and throughout my various tests I used one or the others depending upon what was failing.

Honestly, I'm surprised that this was not happening in versions of Angular prior to 8, so it took me a bit off guard.

How do I prevent a Browser from opening up when I run Angular unit tests?

When you run unit tests using the Angular CLI's ng test command you'll often see a browser open up. Sometimes this is useful if you want to debug the unit tests in the browser, however since I have moved the bulk of my unit test debugging to IntelliJ, this is just a minor annoyance.

You can shut it off. Open up the Karma.conf.js file in the src folder of your Angular CLI project. Find this line:


browsers: ['Chrome'],

This tells Karma to open Chrome to run the tests. But, there is another option:


browsers: ['ChromeHeadless'],

Rerun your tests and you'll find that no browser opens up.

I love it when things are easy.

Why won't my Observable Trigger in a Unit Test?

I've been writing some tests as part of a project I'm working on, and for some reason I could not get a mocked Observable to resolve. This will explain the mistake I made and how to fix it.

The Setup

This component used a resolver to load data before the component loaded. To access a resolver's data, you inject the activated route into the constructor, like this:


constructor(private route:ActivatedRoute){}

In the ngOnInit(), you'd subscribe to the data property:


ngOnInit(){
this.route.data.subscribe((data: {myValue}){
this.myValue = data.myValue
}
);
}

This saves your MyValue data from the resolver into the local component instances myValue.

Writing The Tests

First, I mocked the ActivatedRoute:


class ActivatedRouteMock {
data: Observable<Data>;
}

Then, I created an instance of it:


const activatedRoute = new ActivatedRoutMock();

As part of the TestBed's providers:


{ provide: ActivatedRoute, useValue: activatedRoute}

The first test I wrote was to make sure the subscribe was actively working.

First, I created the observer and component:


const observer;
beforeEach(() =>
{
activatedRoute.data = Observable.create (observer) => {
observer = observer;
}
spyOn(activatedRoute.data, 'subscribe');
component = TestBed.createComponent(myComp).componentInstance;
});

Then, I ran the test:


it('should subscribe to activated route', () =>
{
expect(activatedRoute.data.subscribe).toHaveBeenCalled();
});

This worked great. Now, I wanted to test to make sure that other methods inside the component's result function worked. To do that I'll have manually trigger the observer:


it('should resolve route data and save myValue'. ()=>
}
const myValue = "something";
const results = {};
results['myValue'] = myValue;
observer.next(results);
observer.component();
expect(component.myValue).toBe(myValue);
});

This should work, right? Nope, it failed ever time. The observer was always null. I was scratching my head and adding a lot of breakpoints and outputs trying to figure out what I had done wrong so far.

What is wrong

The function to create the observable is never run until the observable is subscribed to. Clearly we are subscribing to it in the component code:


this.route.data.subscribe((data: {myValue}){
this.myValue = data.myValue
}

So, why doesn't it run? Think about it first, because spoilers are below.

The spy is getting in the way:


spyOn(activatedRoute.data, 'subscribe');

Because of the spy in place, the subscribe() function is intercepted, and as such never run, which never runs the function in the Observable's create function, which never saves the Observer. The solution was simple once I figured out what was going on:


spyOn(activatedRoute.data, 'subscribe').and.callThrough();

Tell the spy to intercept and watch, but still allow the call to go through.

Everything started working wonderfully. :-)

How do you Access the Arguments of a Jasmine spy?

I've been writing unit tests for clients. It's a nice change to finally have some clients who care about such things.

I have this Angular method that creates an object, then emits an event.

The method was something like this:


onButtonClick() {

let eventParameters = {};

// do stuff

this.eventEmitter.emit(eventParameters);
}

I want to be compare the properties of the eventParameters argument with what I expect them to be. However, I have no way to access the eventParameters directly because it is local to the method. My first thought was to do something like this:


spyOn(component,'onButtonClick').and.returnValue(myOwnEventParametersObject)

However my intent was to test the code inside the method, whereas this bypasses calling the method altogether. That would not suffice.

So, how'd I do it?

First, I saved an instance to the Spy:


const mySpy = spyOn(component.eventEmitter, 'emit');

I can drill down into this spy to get the results of the most recent call:


const recentCallArgs = mySpy.calls.mostRecent().args[0];

Once I have the argument object, I can introspect it to compare the arguments created by the method with what I expected them to be.


expect(recentCallArgs.value).toBe('value');

TaDa!

How do I test Observable.timer?

This was trickier than I thought, which is why I'm writing a blog post about it. I'm writing some code which uses an FakeAsync Zone nor RxJS TestScheduler.

The timer() call returns an Observable, so I decided to create one myself which gave me complete control.


let timerObserver :Observer<any>;
beforeEach(() =>
{
spyOn(Observable, 'timer').and.returnValue(Observable.create(
(observer =>{
timerObserver = observer;
})
));
});

I created an Observer object. This is the internal logic that makes the Observable resolve itself. I used spyOn() inside a beforeEach() to have the unit testing framework return my own Observable and ignore the library code. I save the observer for later usage.

Now, when I'm testing I can resolve the timer immediately:


it('Some Test',()=>
{
// do stuff if needed

// trigger the fake timer using the Observer reference
timerObserver.next('');
timerObserver.complete();

//
expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});

I spent more time banging my head on this than I thought I would, and I hope this helps you.

Running Unit Tests from IntelliJ

I put together this screencast about running Unit Tests using IntelliJ.

How do you tell Jasmine to run a single test?

Last week I wrote about how to tell Jasmine to ignore a unit test. This week I'll tell you how to ignore every unit test except one.

To use last week's example, a normal unit test would be something like this:


it("True is True", () =>
{
expect("True").toBe("True");
});

A normal test suite will include lots of tests spread out through lots of files. You can focus a unit test using fit(). This tells Jasmine to only run the unit test with the 'f':


fit("True is True", () =>
{
expect("True").toBe("True");
});

other unit tests will be ignored. This can be a great use when testing something new.

You can use the same thing to disable a full suite with fdescribe() instead of describe:


fdescribe("Some Test Suite"()=>
{
it("True is True", () =>{
expect("True").toBe("True");
});
});

This type of stuff can be pretty handy if you're running your tests via command lines.

How do I tell Jasmine to ignore a test?

Today I learned you can tell Jasmine to ignore a unit test. A normal unit test would be something like this:


it("True is True", () =>
{
expect("True").toBe("True");
});

When running a unit test file with this inside the test will run., You could comment it out if you wish, but that can get confusing for larger unit tests with embedded oomments. Instead you can use xit() instead of it():


xit("True is True", () =>
{
expect("True").toBe("True");
});

Now when you run your unit test, this test will be marked as viewed as pending and will not be run.

You can use the same thing to disable a full suite with describe():


xdescribe("Some Test Suite"()=>
{
it("True is True", () =>{
expect("True").toBe("True");
});
});

This is pretty handy, but something I overlooked when learning all about unit testing.

All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.