Tag: stub


Unit testing: Stubs, Fakes, Mocks

June 24, 2024

Uncategorized

Comments Off on Unit testing: Stubs, Fakes, Mocks


Effective communication with your team is crucial. No matter what terms you use, agreeing on and adapting to the preferences of your team is the most important takeaway.

This is how I think of these terms and how they are used on my team.

Stubs

A stub is a minimal unimplemented module.

This is not specific to unit testing. When you begin working on a module, it will have some required interface to allow it to be coupled to the overall system. Generally, you would “stub out” some minimal code that will allow your system to compile. Once you have a stub, you can work on adding tests and filling in the functionality.

Creating a stub example:

interface UserModuleInterface {
  getUser(): User;
}

class UserModule implements UserModuleInterface {
  getUser() {
    throw new Error('Not implemented');
  }
}

test('User module implements interface', () => {
  let sut = new UserModule();
  expect(sut).toBeInstanceOf(UserModuleInterface);
});

This is just enough to make the project compile/run. The next thing you might do is have it actually return a User, but you don’t have any users so you need to create fake users.

Fakes

A fake is a data object with made-up data.

You could return hard-coded literals, but it’s often useful to have some variety. There are useful generators such as faker to help out.

Creating a fake user example:

import { faker } from '@faker-js/faker';

function createFakeUser() {
  return {
    name: faker.person.fullName(),
    email: faker.internet.email()
  };
}

Great, but we don’t want to create fakes in the real implementation, so how can we use this in our test environment and inject it into the system?

Mocks

A mock is an implementation of a dependency that is injected in.

Mocks are used in unit testing to replace external dependencies. This allows for predictable/testable behavior. For example, mocking network requests allows you to test positive cases and fault cases without the unpredictable nature of actual network calls.

Mocks will often include some metadata so that you can “Spy” on them and understand how they are used after being injected.

In an integration test, the real implementation is used.

Creating a mock example:

interface Database {
  find(key: string): any;
}

class UserModule implements UserModuleInterface {
  private db: Database;

  constructor(db: Database) {
    this.db = db;
  }

  getUser() {
    return this.db.find('user');
  }
}

class MockDatabase implements Database {
  private db: { [key: string]: any } = {};
  calls = { find: 0, insert: 0 };

  find(key: string) {
    this.calls['find'] += 1;
    return this.db[key];
  }

  insert(key: string, value: any) {
    this.calls['insert'] += 1;
    this.db[key] = value;
  }
}

test('UserModule returns user from db', () => {
  let dbMock = new MockDatabase();
  let fakeUser = createFakeUser();
  dbMock.insert('user', fakeUser);
  let sut = new UserModule(dbMock);
  let user = sut.getUser();

  expect(dbMock.calls['find']).toBe(1);
  expect(user).toBe(fakeUser);
});

That is how stubs, fakes, and mocks work together to build up tests and implementations.



Subscribe via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.





Swift Tweets