All Articles

Mock Factories instead of Mock objects

These last months i’ve being practicing Clean architecture and TDD at some of my personal projects (like this one), and with that i’ve learned some new concepts and patterns.

Over the course of the projects, i was using mock objects at my unit tests.

it('should create the user', () => {
  // Arrange
  const mockedUser: UserEntity = {
    name: "mocked name";
    email: "mocked email";
    phone: "mocked phone";
    roles: [Roles.ADMIN];
  }
  
  // Act
  this.usecase.call(mockedUser)
  
  // Assert
})

With the time, those projects was getting bigger, and depending on the layer that i was working on, i need to use the same mock over and over again. So i separated the mocks in different files to use those mocks when i needed to.

// test/mocks/user/user_entity.mock.ts

export const userMock: UserEntity = {
  name: "mocked name";
  email: "mocked email";
  phone: "mocked phone";
  roles: [Roles.ADMIN];
}

If needed, i override some properties just by destructuring the mock and adding different values:

// usecases/register/register.usecase.spec.ts

it('should do something with an ADMIN user', () => {
  // Arrange
  
  // Act
  this.usecase.call(userMock)
  
  // Assert
})

it('should do something with a NORMAL user', () => {
  // Arrange
  
  // Act
  this.usecase.call({ ...userMock, roles: [Roles.NORMAL] })
  
  // Assert
})

That worked just fine for a time, until i decided to try Rich Entities with inner props using Value Objects, so destructuring wouldn’t work this time.

// domain/entities/user.entity.ts

export interface UserEntityProps = {
  name: Name;
  email: Email;
  phone: Phone;
  roles: Roles[]
}

export class UserEntity {
  constructor(props: UserEntityProps) {
    this.props = props;
  }

  private props: UserEntityProps;

  // Getters and Setters
}

Example of how i’ve tried to adapt for the first time, exporting the props object:

// test/mocks/user/user_entity_props.mock.ts

export const userPropsMock: UserEntityProps = {
  name: "mocked name";
  email: "mocked email";
  phone: "mocked phone";
  roles: [Roles.ADMIN];
}
// usecases/register/register.usecase.spec.ts

it('should do something with an ADMIN user', () => {
  // Arrange
  
  // Act
  this.usecase.call(
    new UserEntity(userPropsMock)
  )
  
  // Assert
})

it('should do something with a NORMAL user', () => {
  // Arrange
  
  // Act
  this.usecase.call(
    new UserEntity({ 
      ...userPropsMock, 
      roles: [Roles.NORMAL]
    })
  )
  
  // Assert
})

I wasn’t liking it so much, so i tried to find a new solution. That’s when i watched an event at Rocketseat where they introduced the concept of mock factories.

// test/factories/user/user_entity.factory.ts

type Override = Partial<UserEntityProps>;

export const makeUserMock(
  override: Override = {}
): UserEntity {
  return new UserEntity({
    name: new Name("mocked name");
    email: new Email("mocked email");
    phone: new Phone("mocked phone");
    roles: [Roles.ADMIN];
    ...override,
  });
}
// usecases/register/register.usecase.spec.ts

it('should do something with an ADMIN user', () => {
  // Arrange
  
  // Act
  this.usecase.call(makeUserMock())
  
  // Assert
})

it('should do something with a NORMAL user', () => {
  // Arrange
  
  // Act
  this.usecase.call(
    makeUserMock({
      roles: [Roles.NORMAL]
    })
  )
  
  // Assert
})

With this, i got a scalable and reusable solution when new fields are added =)

Published Feb 7, 2023

I'm a dedicated developer, always interested in learning and using new technologies.