Firebase Cloud Functions Jest Setup with TypeScript & Mocking
Tags: firebase, jest, typescript, serverless
Published: 2021-11-14 14:22:30

# Overview

As the backend requirements grows with 0% test coverage, the harder it is to maintain the project or even scale due to bugs coming out from anywhere. Debugging without tests also takes so much time to have to manually open the app, login using the credentials that you remember for a particular user role, and finally following the necessary steps to replicate the bug only to read off the error message or find out if it's working. It's a waste of time writing functionalities without tests, you didn't actually "save" time by not writing tests. 

# Introduction

We're only going to cover how to setup Jest with TypeScript and a basic unit test and fairly simple mocking to a class that we want to mock a response value of a certain method.

# Setup Jest TypeScript

Setting up Jest on Cloud Functions is fairly simple and straight-forward. Just run the following command and you're ready to go!

```bash
npm i jest @types/jest ts-jest typescript -D
```

The following are the dependencies that our Cloud Functions directory in `functions` directory requires. We'll break it down below of what they are and what's their purpose.

Dependencies:

- `jest` a testing framework maintained by Facebook, works well with frontend frameworks like Vue, React, Angular also with TypeScript. We'll be using Jest for writing up all of our unit tests, integration tests, and other types of tests that you know of.
- `@types/jest` all the TypeScript typings are found here, we're getting the auto complete sugar that we developers love
- `ts-jest` there are some utility functions that with help us mock a few functions or methods from a third party library
- `typescript` our favorite tool

Once all the dependencies are installed, then all is cool! Next up is we create a file called `jest.config.js` at the root of our `functions` directory and we'll have the following lines of code in the newly created file.

```jsx
module.exports = {
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
}
```

Now we'll add a script in our `package.json` file on key `scripts`

```json
// package.json
{
	"scripts": {
		// ... other scripts
		"test": "jest"
	}
}
```

Now by the following setup that we have, we will have all of our tests suites housed inside `src` directory. Here are the following examples on how the structure would look.

```jsx
functions
	- src
		- todo
			- todo.service.ts
			- todo.spec.ts // or you can name it as "todo.test.ts"
		- todo-b
			- todo-b.service.ts
			- todo-b.spec.ts // or you can name it as "todo-b.test.ts"
```

And when we want to execute or run our tests cases, we'll use the following command

```bash
jest # or you can run with "npm run test" or "npm test"
```

You can read more for the [full documentation from here](https://basarat.gitbook.io/typescript/intro-1/jest)

# Writing our First Unit Test

Suppose that `TodoService` (relative path `todo/todo.service.ts`) is the class we want to test.

```tsx
// todo.service.ts

export class TodoService {
	public giveMeANumber(number: number): number {
		return number;
	}
}
```

Then we will proceed to create a test/spec file in the same directory as the service so we can easily find its corresponding test file.

```tsx
// todo.service.spec.ts

import { TodoService } from './todo.service.ts'

describe('TodoService', () => {
	let service: TodoService

	beforeEach(() => {
		service = new TodoService()
	})

	describe('giveMeANumber', () => {
		test('when value is true, then should return true', () => {
			expect(true).toBe(true)
		})
	
		it('should return 5, when given parameter is 5', () => {
			const number = service.giveMeANumber(5)
		
			expect(number).toBe(5)
		})
	})
})
```

Notice how we did not import `describe`, `test`, `beforeEach`, `it`, and `expect` functions because the setup that we have for Jest already handled it for us, the functions are available on all `*.spec.ts` or `*.test.ts` files. And lastly it's the `@types/jest` that will give us the convenience of auto-completes so we don't have to worry if we're passing down the correct arguments into those functions.

The `describe` function describes the test suite or it groups a set of test cases in it. It is very similar to `group` when we are writing tests in Flutter. And the `test` function is the actual test case.

And now to run our tests we will use the following command in the terminal. Suppose that you are in the root directory of the project, in this case it's `./functions` 

```bash
jest # `npm test` or `npm run test`
```

*Note: It will take about under 10s to run the test, not sure why it's this slow.*

Then that should display the test results in your terminal.

When you want to run specific tests, you can use `jest -t 'when value is true, then should return true'` make sure it matches that test case you want to run.

### Writing our First Test with Mocking

And now we are going to make mocks to any third party libraries that we depend on so we don't have to make the test await for any HTTP calls.

Say `TodoService` will have a dependency for `AwesomeTodoAPI` where we want to make a call only to retrieve outstanding todos. And we just want to assert the array length of the response data just to make an assertion example as simple as possible. The only thing we want to learn now is how we can create mocks in Jest with TypeScript.

Now let's have the same service class but is dependent to a library `AwesomeTodoAPI` where this dependency will handle the HTTP calls internally, so we're only calling methods from it that will give us the resources we require.

```tsx
// todo.service.ts

import AwesomeTodoAPI from '@awesome/AwesomeTodoAPI'

type AwesomeTodo = {
	title: string
	description: string
}

export class TodoService {
	private awesomeTodoApi: AwesomeTodoAPI

	constructor() {
		this.awesomeTodoApi = new AwesomeTodoAPI()
	}

	public async fetchOutstandingTodos(): Promise<AwesomeTodo[]> {
		const response = await this.awesomeTodoApi.fetchOutstandingTodos()
		return response.data
	}

	public giveMeANumber(number: number): number {
		return number;
	}
}
```

The method `fetchOutstandingTodos` inside our `TodoService` is dependent on the returned response from `AwesomeTodoAPI` method `fetchOutstandingTodos`. We can then proceed to creating a mock of the returned response for that. Without mocking, it will still make the actual call for `fetchOutstandingTodos` but that'll take some time before the data gets returned and will result into making our tests running slow, and that's not what we want in this case since we'll only want to unit test our code and not make an integration test.

On the same spec/test file that we have,

```tsx
// todo.service.spec.ts

import { mocked } from "ts-jest/utils";
import { TodoService } from './todo.service.ts'
import { AwesomeTodoAPI } from '@awesome/AwesomeTodoAPI'

// Let Jest automatically create all the mocks for us
jest.mock('@awesome/AwesomeTodoAPI')

describe('TodoService', () => {
	let service: TodoService
	const mockedAwesomeTodoApi = mocked(AwesomeTodoAPI, true)

	beforeEach(() => {
		service = new TodoService()

		// Clear out all the mocks that we 
		// created before each test case
		mockedAwesomeTodoApi.mockClear()
	})

	describe('fetchOutstandingTodos', () => {
		test('when called, should return all the outstanding todos', async () => {
			// Arrange: Mock the returned response value
			AwesomeTodoAPI.prototype.fetchOutstandingTodos = jest.fn()
				.mockImplementationOnce(() => {
					return {
						data: [
							{ title: 'Todo A', description: 'lorem', },
							{ title: 'Todo B', description: 'lorem', },
						] as AwesomeTodo[]
					} as AwesomeTodoAPIResponse
				})
	
			// Action
			const todos = await service.fetchOutstandingTodos()
	
			// Assert
			expect(todos.length > 1).toBeTruthy()
		})

		test('when there are no outstanding todos, should return empty array', async () => {
			// Arrange: Mock the returned response value
			AwesomeTodoAPI.prototype.fetchOutstandingTodos = jest.fn()
				.mockImplementationOnce(() => {
					return {
						data: [] as AwesomeTodo[]
					} as AwesomeTodoAPIResponse
				})
	
			// Action
			const todos = await service.fetchOutstandingTodos()
	
			// Assert
			expect(todos.length === 0).toBeTruthy()
		})
	})
})
```

Now to break it down for you on what's happening:

- We imported `AwesomeTodoAPI` from `'@awesome/AwesomeTodoAPI'` and automatically created a mock for all of its method by calling `jest.mock(importPath, factoryCallback)` (You can learn more about the `factoryCallback` I am referring to from the official Jest docs if you wish to, basically you just have to match all method names and its return values, and have the callback return an object. It's obviously a boilerplate code but maybe there can be a potential use case for manually setting up mocks)
- Next is we create a variable called `mockedAwesomeTodoApi` where this is the actual mock and thanks to the utility function `mocked` from `ts-jest` where we pass down in the first argument of the object we want to mock and its second argument is if we want to mock it deep so we set `true`
- Inside `beforEach` is we setup our service `TodoService` and clear out all the mocks that was created previously from each test case, so before each test case will be a fresh mock by calling `mockClear` from the mocked object
- Now inside our test suite for method `fetchOutstandingTodos` on the "Arrange" section is we setup the return response value of the `AwesomeTodoAPI` for its method `fetchOutstandingTodos` since we don't want to make an actual call to the API. The way we mock the returned response is by accessing the class prototype itself, and access `fetchOutstandingTodos` and assign a new value to it by what is returned in the callback function for `mockImplementationOnce`
- Then we make the actual call from our `TodoService` that calls `fetchOutstandingTodos` from `AweomseTodoAPI` and pass down the returned value to the `todos` variable
- Finally we assert if `todos` contains some data or if it's empty.

# Wrapping Up

We learned how to setup Jest with TypeScript in just a few minutes. Now we can get our tests up and running in no time!

Thanks for taking the time to read. Good day!