Skip to content

Why Mocklify?

mattwilson1024 edited this page May 17, 2020 · 2 revisions

Mock data is a crucial element in many aspects of software development. For example:

  • For passing into functions, classes or components when writing tests
  • For passing as props into components when viewing them with Storybook
  • For simulated demos or examples

Defining and using mock data is generally straightforward but, without care, mock data can quickly become messy, unwieldy and hard to maintain over time (as the codebase grows in size or complexity).

Mocklify aims to ease this pain, providing a simple yet powerful API for working with mock data in a maintainable and declarative manner.

Let's explore an example...

Suppose our application keeps track of a list of users. One of the models in our app is a simple IUser type as below:

export interface IUser {
  id: string;
  firstName: string;
  lastName: string;
  isAdmin: boolean;
}

In order to test the "user list" feature, we might start by writing a test which defines some mock users, then passes them into the component to test it, like so:

it('should show the users in the UI', () => {
  const mockData: IUser[] = [
    {
      id: 'user1',
      firstName: 'Harry',
      lastName: 'Potter',
      isAdmin: false
    },
    ...
  ];

  // ... pass the data into the component and assert that everything is displayed correctly
});

Simple enough. Next, let's imagine that we need to write a second test to check that the component renders correctly when presented with admin users.

A naive approach might be to define a second set of mock data, but this time our users will be admins:

it('should display a badge next to admin users', () => {
  const mockData: IUser[] = [
    {
      id: 'user1',
      firstName: 'Harry',
      lastName: 'Potter',
      isAdmin: true
    },
    ...
  ];

  // ... pass the data into the component and assert that everything is displayed correctly
});

We've only written two simple tests, but we already have problems arising:

  1. Duplication - Both tests had to define a set of users, even though they are mostly the same. If many items are needed, or the objects are long and complex, this could result in a huge amount of duplicated code and effort.
  2. Lack of clarity - The mock data for these two tests is almost identical, the only difference is the isAdmin property. For someone reading this code, it would take cognitive effort to identify that isAdmin is the changing variable here, especially if the objects are larger or more complex.
  3. Hard to maintain - As our app evolves, the IUser object might change. Such changes would potentially mean adjusting large numbers of mock definitions, otherwise they might become stale and inconsistent.

Experienced developers may address these shortcomings by defining the mock data once and using code to manipulate it in the tests. For example:

const mockData: IUser[] = [
  ...
];

it('should display a badge next to admin users', () => {
  const testData = mockUsers.map(user => {
    return {
      ...user,
      isAdmin: true
    };
  });
  
  // ... pass the data into the component and assert that everything is displayed correctly
});

This avoids many of the problems identified above, but in many situations, the code would have the potential to become hard to interpret, especially if the changes are non-trivial or involve nested data structures.

Enter Mocklify.

Mocklify provides a powerful, chained API which allows complex selection, filtering, transformation and projection - all in a way that reads like a sentence.

The code example below shows how we might achieve the same task with Mocklify, but crucially this can be extended to perform a range of complex transformations as shown in the documentation.

const mockData: IUser[] = [
  ...
];

it('should display a badge next to admin users', () => {
  const mockData = mocklify<IUser>()
    .addAll(MOCK_USERS)               // start with the base mocks
    .mutate(
      override({ isAdmin: true }),    // override the isAdmin property for each item
    )
    .getAll();

  // ... pass the data into the component and assert that everything is displayed correctly
});
Clone this wiki locally