Hone logo
Hone
Problems

Jest Dummy Object Creation Challenge

Testing often requires creating mock or "dummy" objects to simulate dependencies and isolate the code under test. This challenge focuses on effectively creating such dummy objects in Jest using TypeScript, a common and powerful pattern for robust testing.

Problem Description

Your task is to implement a testing scenario where you need to create dummy objects to satisfy dependencies of a function or class you are testing. You will be provided with a simple scenario involving a UserService that depends on a UserRepository. Your goal is to create a mock UserRepository that the UserService can use during testing, allowing you to control its behavior and verify interactions.

What needs to be achieved: Create a dummy implementation of a UserRepository interface that can be injected into a UserService for testing purposes.

Key requirements:

  1. Define an interface for UserRepository with at least two methods (e.g., getUserById and saveUser).
  2. Define a UserService class that takes an instance of UserRepository in its constructor.
  3. Implement a Jest test suite for UserService.
  4. Within the test suite, create a mock/dummy implementation of the UserRepository interface.
  5. The mock UserRepository should allow you to control the return values of its methods.
  6. The mock UserRepository should allow you to spy on method calls to verify interactions.
  7. Inject the dummy UserRepository into the UserService instance within your tests.
  8. Write test cases to verify UserService behavior using the dummy repository.

Expected behavior:

  • Tests should pass when the UserService is configured with a correctly mocked UserRepository.
  • The tests should demonstrate the ability to:
    • Mock specific return values for repository methods.
    • Assert that repository methods were called with expected arguments.

Edge cases to consider:

  • What happens if a getUserById call doesn't find a user?
  • How do you mock different return values for the same method in different test scenarios?

Examples

Example 1: Mocking getUserById to return a user

// Assume the following interfaces and classes are defined elsewhere:

interface User {
  id: number;
  name: string;
}

interface UserRepository {
  getUserById(id: number): Promise<User | undefined>;
  saveUser(user: User): Promise<void>;
}

class UserService {
  constructor(private userRepository: UserRepository) {}

  async getUserProfile(userId: number): Promise<string | null> {
    const user = await this.userRepository.getUserById(userId);
    if (user) {
      return `User: ${user.name}`;
    }
    return null;
  }
}

// In your test file (e.g., userService.test.ts)

import { UserService } from './userService'; // Assuming files are structured correctly

describe('UserService', () => {
  it('should return user profile when user exists', async () => {
    // Create a mock UserRepository
    const mockUserRepository: UserRepository = {
      getUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
      saveUser: jest.fn(), // We don't need to mock this for this specific test
    };

    const userService = new UserService(mockUserRepository);
    const profile = await userService.getUserProfile(1);

    expect(profile).toBe('User: Alice');
    expect(mockUserRepository.getUserById).toHaveBeenCalledWith(1);
  });
});

Output: The test case above would pass, verifying that UserService.getUserProfile correctly retrieves and formats user data when the UserRepository provides it.

Example 2: Mocking getUserById to return undefined (user not found)

// Using the same User, UserRepository, and UserService definitions as Example 1

describe('UserService', () => {
  it('should return null when user does not exist', async () => {
    const mockUserRepository: UserRepository = {
      getUserById: jest.fn().mockResolvedValue(undefined), // User not found
      saveUser: jest.fn(),
    };

    const userService = new UserService(mockUserRepository);
    const profile = await userService.getUserProfile(99); // Non-existent ID

    expect(profile).toBeNull();
    expect(mockUserRepository.getUserById).toHaveBeenCalledWith(99);
  });
});

Output: This test would also pass, demonstrating that UserService.getUserProfile correctly handles cases where the user is not found in the repository.

Constraints

  • TypeScript Version: Use a recent version of TypeScript (e.g., 4.x or 5.x).
  • Jest Version: Use a recent version of Jest (e.g., 28.x or 29.x).
  • Mocking Library: Use Jest's built-in mocking capabilities (jest.fn(), jest.spyOn(), etc.). You are not expected to use external mocking libraries.
  • Async Operations: The repository methods are asynchronous (return Promise). Your mocks must correctly handle asynchronous behavior.
  • Code Structure: Assume standard project structure where your source code is in a src directory and tests are in a __tests__ or .test.ts suffix.

Notes

  • Consider the different ways Jest allows you to mock:
    • Manual Mocks: Implementing the interface yourself, as shown in the examples.
    • jest.mock(): For mocking modules (not directly applicable here as we're mocking an interface implementation, but good to be aware of).
    • jest.spyOn(): Useful for spying on methods of existing objects or classes.
  • Focus on clarity and readability in your mock implementations. The goal is to make your tests understandable.
  • Think about how you would test the saveUser method if it were part of the UserService's responsibility. What would you assert?
Loading editor...
typescript