Unit testing map functions with Jest
Tags: javascript, typescript, testing, programming
Published: 2022-09-16 13:57:43

I ran into problems when I was unit testing map functions with Jest. I gave the array map only the reference to a function and as a result I was in trouble. Here's how it happened.

```typescript
import { functionInsideMap } from './functionInsideMap';

export function referenceToArrowFuncInMap(names: string[]): string[] {
	const namesWithHello = names.map(functionInsideMap);
	return namesWithHello;
}
```

The referenceToArrowFuncInMap above applies the functionInsideMap to each array value. Let's write a unit test for the referenceToArrowFuncInMap.

```typescript
import { referenceToArrowFuncInMap } from './index';
import { functionInsideMap } from './functionInsideMap';

jest.mock('./functionInsideMap');
const mockedFunctionInsideMap = jest.mocked(functionInsideMap, false);

describe("Test the 'referenceToArrowFuncInMap' function", () => {
	beforeEach(() => {
		mockedFunctionInsideMap.mockReset();
	});

	it('Using reference to function inside map will fail', () => {
		//Arrange
		//Setting inputs
		const names = ['John', 'Matt'];

		//Setting output
		const expectedOutput = ['Hello John!', 'Hello Matt!'];

		//Mocking functions and objects
		mockedFunctionInsideMap
			.mockReturnValueOnce('Hello John!')
			.mockReturnValueOnce('Hello Matt!');

		//Act
		const callOutput = referenceToArrowFuncInMap(names);

		//Assert output
		expect(callOutput).toEqual(expectedOutput);

		//Assert function under test internals
		expect(functionInsideMap).toHaveBeenCalledTimes(2);
		expect(functionInsideMap).toHaveBeenNthCalledWith(1, 'John');
		expect(functionInsideMap).toHaveBeenNthCalledWith(2, 'Matt');
	});
});
```

I get the following error from Jest when I run the above unit test. The `toHaveBeenNthCalledWith` assert for functionInsideMap seems to break.

```bash
> unit-testing-map-functions-with-jest@1.0.0 test
> jest

 FAIL  src/index.referenceToArrowFuncInMap.spec.ts
  Test the 'referenceToArrowFuncInMap' function
    ✕ Using reference to function inside map will fail (11 ms)

  ● Test the 'referenceToArrowFuncInMap' function › Using reference to function inside map will fail

    expect(jest.fn()).toHaveBeenNthCalledWith(n, ...expected)

    n: 1
    Expected: "John"
    Received
    ->     1: "John", 0, ["John", "Matt"]
           2: "Matt", 1, ["John", "Matt"]

    Number of calls: 2

      31 |              //Assert function under test internals
      32 |              expect(functionInsideMap).toHaveBeenCalledTimes(2);
    > 33 |              expect(functionInsideMap).toHaveBeenNthCalledWith(1, 'John');
         |                                        ^
      34 |              expect(functionInsideMap).toHaveBeenNthCalledWith(2, 'Matt');
      35 |      });
      36 | });

      at Object.<anonymous> (src/index.referenceToArrowFuncInMap.spec.ts:33:29)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.79 s
Ran all test suites.
```

Jest expected the functionInsideMap to have been called with value "John". But instead map called it with 3 values.
- "John"
- 0
- ["John", "Matt"]

## Reason
This is because Javascript array map [calls your callback-function with three arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#parameters).

> 1. element - The current element being processed in the array.
2. index - The index of the current element being processed in the array.
3. array - the array map was called upon.
[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#parameters)

If you give a function reference to the array map function Jest records all arguments given. As a result the error shows that map called functionInsideMap with 3 arguments. Not with 1.

The easiest way to fix this is to change the source code. Firstly use an anonymous function as the map's callback. Secondly call the functionInsideMap inside the anonymous function.

## The fix
```typescript
import { functionInsideMap } from './functionInsideMap';

export function referenceToArrowFuncInMap(names: string[]): string[] {
	const namesWithHello = names.map((name) => functionInsideMap(name));
	return namesWithHello;
}
```

This way the anonymous function only uses the first given argument and Jest works the right way.

## Conclusion
In conclusion it's better to use concise functions as the callback functions. Firstly this makes your unit testing easier. Additionally the readability of the code also improves. The reader clearly sees what the array map passes to the callback. Unit testing map functions with concise functions makes your life easier.