Simplifying jest stubs using jest-when
Tags: javascript, beginners, webdev, programming
Published: 2023-02-10 19:40:32

---
title: Simplifying jest stubs using jest-when
published: true
description: 
tags: 
- javascript 
- beginners
- webdev
- programming
canonical_url: https://codesai.com/posts/2022/09/jest-when-stubs
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/semvbe69c58ueqitorjn.jpeg
# Use a ratio of 100:42 for best results.
# published_at: 2023-02-10 19:34 +0000
---

In a recent deliberate practice session with some developers from [Audiense](https://audiense.com/) (with whom we’re doing the [Codesai’s Practice Program](https://github.com/Codesai/practice_program_js) twice a month), we were solving the [Unusual Spending Kata
](https://kata-log.rocks/unusual-spending-kata) in JavaScript.

While test-driving the `UnusualSpendingDetector` class we found that writing [stubs](http://xunitpatterns.com/Test%20Stub.html) using plain [jest](https://jestjs.io/) can be a bit hard. The reason is that [jest](https://jestjs.io/) does not match mocked function arguments, so to create stubbed responses for particular values of the arguments we are forced to introduce logic in the tests.

Have a look at a fragment of the tests we wrote for `UnusualSpendingDetector` class:

```javascript
import {UnusualSpendingDetector} from "../src/UnusualSpendingDetector";

describe('UnusualSpendingDetector', () => {
  const currentMonth = '2020-02';
  const previousMonth = '2020-01';
  const userId = '1234';
  let paymentsRepository;
  let detector;

  beforeEach(() => {
    const calendar = {
      getCurrentMonth: () => currentMonth,
      getPreviousMonth: () => previousMonth,
    };
    paymentsRepository = {
      find: jest.fn()
    }
    detector = new UnusualSpendingDetector(paymentsRepository, calendar);
  });

  // more tests ...
  
  test(
    'detects an unusual spending when spending for a category in consecutive months grew 50% or more', 
    () => {
      const currentMonthPayments = [payment('food', 200, currentMonth)];
      const previousMonthPayments = [payment('food', 100, previousMonth)];
      paymentsRepositoryWillReturn(currentMonthPayments, previousMonthPayments);

      const unusualSpendings = detector.detect(userId);

      expect(unusualSpendings).toEqual([unusualSpending('food', 200)]);
  });
  
  // more tests ...

  function payment(category, amount, month) {
    return {category, amount, month};
  }

  function unusualSpending(category, amount) {
    return {category, amount};
  }

  function paymentsRepositoryWillReturn(currentMonthPayments, previousMonthPayments) {
    paymentsRepository.find.mockImplementation((userId, month) => {
      if (month === currentMonth) {
        return currentMonthPayments;
      } else if (month === previousMonth) {
        return previousMonthPayments;
      }
    });
  }
});
```

Notice the `paymentsRepositoryWillReturn` helper function, we extracted it to remove duplication in the tests. In this function we had to add explicit checks to see whether the arguments of a call match some given values. It reads more or less ok, but we were not happy with the result because we were adding logic in test code (see [all the test cases](https://gist.github.com/trikitrok/1e4fb27cc6acf16acc1cffd5d97a69de)). 

Creating these stubs can be greatly simplified using a library called [jest-when](https://www.npmjs.com/package/jest-when) which helps to write stubs for specifically matched mocked function arguments. Have a look at the same fragment of the tests we wrote for `UnusualSpendingDetector` class now using [jest-when](https://www.npmjs.com/package/jest-when):

```javascript
import {UnusualSpendingDetector} from "../src/UnusualSpendingDetector";
import {when} from 'jest-when';

describe('UnusualSpendingDetector', () => {
  const currentMonth = '2020-02';
  const previousMonth = '2020-01';
  const userId = '1234';
  let paymentsRepository;
  let detector;

  beforeEach(() => {
    const calendar = {
      getCurrentMonth: () => currentMonth,
      getPreviousMonth: () => previousMonth,
    };
    paymentsRepository = {
      find: jest.fn()
    }
    detector = new UnusualSpendingDetector(paymentsRepository, calendar);
  });

  // more tests ...

  test('detects an unusual spending when spending for a category in consecutive months grew 50% or more', () => {
    when(paymentsRepository.find).calledWith(userId, currentMonth)
      .mockReturnValue([payment('food', 200, currentMonth)]);
    when(paymentsRepository.find).calledWith(userId, previousMonth)
      .mockReturnValue([payment('food', 100, previousMonth)]);

    const unusualSpendings = detector.detect(userId);

    expect(unusualSpendings).toEqual([unusualSpending('food', 200)]);
  });
  
  // more tests ...

  function payment(category, amount, month) {
    return {category, amount, month};
  }

  function unusualSpending(category, amount) {
    return {category, amount};
  }
});
```

Notice how the `paymentsRepositoryWillReturn` helper function is not needed anymore, and how the fluent interface of [jest-when](https://www.npmjs.com/package/jest-when) feels nearly like canonical [jest](https://jestjs.io/) syntax. 

We think that using [jest-when](https://www.npmjs.com/package/jest-when) is less error prone than having to add logic to write your own stubs with plain [jest](https://jestjs.io/), and it’s as readable as or more than using only [jest](https://jestjs.io/) (see [all the refactored test cases](https://gist.github.com/trikitrok/d26432d888aef427016d8bc5fed58ab3)). 

To improve readability we played a bit with a functional builder. This is the same fragment of the tests we wrote for `UnusualSpendingDetector` class now using a functional builder over [jest-when](https://www.npmjs.com/package/jest-when):

```javascript
import {UnusualSpendingDetector} from "../src/UnusualSpendingDetector";
import {stub} from "./PaymentsRepositoryHelper";

describe('UnusualSpendingDetector', () => {
  const currentMonth = '2020-02';
  const previousMonth = '2020-01';
  const userId = '1234';
  let paymentsRepository;
  let detector;
  let knowingThat;

  beforeEach(() => {
      paymentsRepository = {
        find: jest.fn()
      };
      knowingThat = stub(paymentsRepository);
      const calendar = {
        getCurrentMonth: () => currentMonth,
        getPreviousMonth: () => previousMonth,
      };
      detector = new UnusualSpendingDetector(paymentsRepository, calendar);
    }
  );
  
  // more tests ...
   
  test(
    'detects an unusual spending when spending for a category in consecutive months grew 50% or more', 
    () => {
      knowingThat().inMonth(currentMonth).userWith(userId).hasPaid(
        [payment('food', 200, currentMonth)])
        .andThat().inMonth(previousMonth).userWith(userId).hasPaid(
        [payment('food', 100, previousMonth)]);

      const unusualSpendings = detector.detect(userId);

      expect(unusualSpendings).toEqual([unusualSpending('food', 200)]);
  });

  // more tests ...

  function payment(category, amount, month) {
    return {category, amount, month};
  }

  function unusualSpending(category, amount) {
    return {category, amount};
  }
});
```

where the `PaymentsRepositoryHelper` is as follows:

```javascript
import {when} from "jest-when";

export function stub(paymentsRepository) {
  const monthPaymentsBuilder = {
    inMonth: inMonth
  };

  return knowingThat;

  function knowingThat() {
    return monthPaymentsBuilder;
  }

  function inMonth(month) {
    return {
      userWith: (userId) => {
        return {
          hasPaid: (payments) => {
            when(paymentsRepository.find).calledWith(userId, month)
              .mockReturnValue(payments);
            return {
              andThat: knowingThat
            };
          }
        };
      }
    };
  }
}
```

In this last version we were just playing a bit with the code in order to find a way to configure the stubs both in terms of the domain and without [free variables](https://en.wikipedia.org/wiki/Free_variables_and_bound_variables) (have a look at [all the test cases using this builder](https://gist.github.com/trikitrok/6ae7a113b0c1651445bb75dd867938fe)). In any case, we think that the previous version using [jest-when](https://www.npmjs.com/package/jest-when) was already good enough.

Probably you already knew [jest-when](https://www.npmjs.com/package/jest-when), if not, give it a try. We think it can help you to write simpler stubs if you're using [jest](https://jestjs.io/).