Gremlins In Angular Testing Pt.I
Tags: angular, testing
Published: 2020-05-26 13:26:47

This is the first part of a series of writings where I describe an issue I was having while writing tests in Angular. These *gremlins* are typically small errors I made that took me way too long to find, considering how quickly I was able to correct them once they had been identified. I have a growing list of them that I’ve been keeping track of.

Initially, I created this list so that I could look back at my own oversight and have a good chuckle. I’m sharing them so that you too might have a good chuckle, but maybe these will actually prevent someone from making the same mistakes someday.

The gremlin I want to share in this article is a particularly nuanced one. It has to do with a mock class I was using to replace the **AngularFirestore** service, from the **AngularFire** library. Here is the mock class: (I’m sure someone out there will already see the problem.)

```typescript
export class MockAngularFirestore {
	public add(data: any) {
		return Promise.resolve({ data: 'doc reference data', id: 'new_id' });
	}

	public delete() {
		return Promise.resolve();
	}

	public update(data: any) {
		return Promise.resolve();
	}

	public valueChanges(options?: any) {
		return of([
			{ data: 'some_data1', id: 'id1' },
			{ data: 'some_data2', id: 'id2' },
			{ data: 'some_data3', id: 'id3' }
		]);
	}

	public collection(path: string) {
		return {
			valueChanges: this.valueChanges,
			doc: this.doc,
			add: this.add
		};
	}

	public doc(id: string) {
		return {
			valueChanges: this.valueChanges,
			delete: this.delete,
			update: this.update
		};
	}
}
```

Here is the failing test: (There was also another failing test for an **updatePost** method as well, but it was failing for the same reason; that test is almost identical to the one shown below.)

```typescript
it('should have a funciton for deleting a blog post specified by ID that calls the appropriate Firestore service funcitons', async () => {
		const id = 'doc_id';
		const collectionSpy = spyOn(TestBed.inject(AngularFirestore), 'collection').and.callThrough();
		const docSpy = spyOn(TestBed.inject(AngularFirestore), 'doc').and.callThrough();
		const deleteSpy = spyOn(TestBed.inject(AngularFirestore), <any>'delete').and.callThrough();

		await service.deletePost(<string>id);

		expect(collectionSpy).toHaveBeenCalled();
		expect(collectionSpy).toHaveBeenCalledWith(<any>'blog');
		expect(docSpy).toHaveBeenCalled();
		expect(docSpy).toHaveBeenCalledWith(<any>id);
		expect(deleteSpy).toHaveBeenCalled();
	});
```

See the problem? Here’s the only clue I had:

![Logged error](https://dev-to-uploads.s3.amazonaws.com/i/3b946el0n3585j34kynz.PNG)

I’m sure there are more experienced devs out there that already know what the problem is, but I did not at the time. I spent a good chunk of time being confused, whispering vulgarities at my machine. I felt like I was going crazy. (It’s going to happen one day, I’m sure.) As it turns out, the issue sprouted from my lack of familiarity with the nuances of using the keyword **this**.

Eventually, I decided to import my mock class into one of my components, created a class method within said component to call the function I was testing, and bound a click event to call said method. I was getting the same error in my browser’s dev console. Then I modified the doc method of my mock class to log this to the console:

```typescript
	public doc(id: string) {
		console.log(this);
		return {
			valueChanges: this.valueChanges,
			delete: this.delete,
			update: this.update
		};
	}
```

This is where I realized the error of my ways; here is what printed to the console:

![Log of "this"](https://dev-to-uploads.s3.amazonaws.com/i/hsql4eggxcq6esgd1tzr.PNG)

Do you know what the problem is now? In case you don’t: the keyword **this** is referring to the **collection** method of my mock class and not the mock class itself. I felt pretty stupid, but programming has always made me feel stupid on a regular basis; it’s all business as usual. So how to rectify the situation? I made a simple modification to the collection method like so:

```typescript
public collection(path: string) {
		return {
			valueChanges: this.valueChanges,
			doc: this.doc,
			add: this.add,
			delete: this.delete
		};
	}
```

Now the **delete** method is within the scope of the keyword **this**, and my test passed with no problems. 

Looking back, I created this gremlin on accident, because I wanted to be able to spy on all of the functions of my mock class directly from the class itself. I’m sure there are better ways I could have written my mock class that would have caused me less trouble, but I digress. 

Thank you for indulging in my recollection of a silly mistake!