0
votes

How might a Flow.js interface be mocked with Jest? To my surprise, I haven't found this issue addressed anywhere.

I'm fairly new to both, but the only (untested) option I see is to create a class that inherits from the interface and then mock the implementing class. This seems quite cumbersome and I don't believe I could place the implementing classes (which are what would actually be mocked) inside the __mocks__ folders expected by Jest and still get the expected behavior.

Any suggestions? Is there a more appropriate mocking tool?

Update

Why do I want to create a mock for an interface? This code intends to have a clean separation of the domain and implementation layers, with the domain classes using Flow interfaces for all injected dependencies. I want to test these domain classes. Using a mocking tool could ideally allow me to more easily and expressively modify the behavior of the mocked services and confirm that the domain class being tested is making the appropriate calls to these mocked services.

Here's a simplified example of a class that I would be testing in this scenario. UpdateResources would be the class under test, while ResourceServer and ResourceRepository are interfaces for services that I would like to mock and 'spy' upon:

// @flow

import type { ResourceServer } from '../ResourceServer';
import type { ResourceRepository } from '../ResourceRepository';

/**
 * Use case for updating resources
 */
export default class UpdateResources {
  resourceServer: ResourceServer;
  resourceRepository: ResourceRepository;

  constructor(resourceServer: ResourceServer, resourceRepository: ResourceRepository) {
    this.resourceServer = resourceServer;
    this.resourceRepository = resourceRepository;
  }

  async execute(): Promise<boolean> {
    const updatesAvailable = await this.resourceServer.checkForUpdates();

    if (updatesAvailable) {
      const resources = await this.resourceServer.getResources();
      await this.resourceRepository.saveAll(resources);
    }

    return updatesAvailable;
  }
}

A solution

The approach I've arrived at which seems to work quite well for my purposes is to create a mock implementation of the interface in the __mocks__ directory what exposes jest.fn objects for all implemented methods. I then instantiate these mock implementations with new and skip any use of jest.mock().

__mocks__/MockResourceServer.js

import type { ResourceServer } from '../ResourceServer';

export default class MockResourceServer implements ResourceServer {

  getResources =  jest.fn(() => Promise.resolve({}));

  checkForUpodates = jest.fn(() => Promise.resolve(true));
}

__mocks__/MockResourceRepository.js

import type { ResourceRepository } from '../ResourceRepository';

export default class MockResourceRepository implements ResourceRepository {
  saveAll = jest.fn(() => Promise.resolve());
}

__tests__/UpdateResources.test.js

import UpdateResources from '../UpdateResources';
import MockResourceRepository from '../../__mocks__/MockResourceRepository';
import MockResourceServer from '../../__mocks__/MockResourceServer';

describe('UpdateResources', () => {

  describe('execute()', () => {
    const mockResourceServer = new MockResourceServer();
    const mockResourceRepository = new MockResourceRepository();

    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should check the ResourceServer for updates', async () => {
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceServer.checkForUpdates).toHaveBeenCalledTimes(1);
    });

    it('should save to ResourceRepository if updates are available', async () => {
      mockResourceServer.load.mockResolvedValue(true);
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceRepository.saveAll).toHaveBeenCalledTimes(1);
    });

    it('should NOT save to ResourceRepository if NO updates are available', async () => {
      mockResourceServer.load.mockResolvedValue(false);
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceRepository.saveAll).not.toHaveBeenCalled();
    });
  });
});

If anyone can offer any improvements, I'm open!

1
why do you need that? what's the point? - skyboyer
I'm not sure the concept applies in this case. What exactly would you be trying to test by mocking a flow interface? Maybe an example of a test you would like to write would help? - Lyle Underwood
Thanks guys. I've updated my question to better explain what I'm trying to do, and added the solution I've arrived at-- which seems to work quite well. If you have any suggestions for improvements, they are appreciated! - HolySamosa

1 Answers

0
votes

The thing is, you don't actually need to mock an implementation of an interface. The purpose of a mock is to 'look like' the real thing, but if you already have an interface that says what the real thing should look like, any implementation that conforms to the interface will automatically serve equally well as a mock. In fact, from the point of view of the typechecker, there won't be a different between the 'real' and the 'mock' implementation.

Personally what I like to do is to create a mock implementation that can be constructed by feeding it mock responses. Then it can be reused in any test case by constructing it directly in that test case with the exact responses it should provide. I.e., you 'script' the mock with what it should say by injecting the responses at the time of construction. The difference between it and your mocking implementation is that if it doesn't have a response, it throws a exception and fails the test. Here's an article I wrote that shows this method: https://dev.to/yawaramin/interfaces-for-scaling-and-testing-javascript-1daj

With this technique, a test case might look like this:

it('should save to ResourceRepository if updates are available', async () => {
  const updateResources = new UpdateResources(
    new MockResourceServer({
      checkForUpdates: [true],
      getResources: [{}],
    }),
    new MockResourceRepository({
      saveAll: [undefined],
    }),
  );

  const result = await updateResources.execute();
  expect(result).toBeTruthy();
});

What I like about these mocks is that all the responses are explicit, and show you the sequence of calls that are happening.