Hone logo
Hone
Problems

Dynamic Jest Test Generation

This challenge focuses on automating the creation of Jest tests based on a given set of test cases. This is a common practice for ensuring comprehensive test coverage, especially when dealing with frequently changing logic or a large number of similar test scenarios. You will create a function that takes an array of test data and dynamically generates Jest test suites and individual tests.

Problem Description

Your task is to implement a TypeScript function that generates Jest tests dynamically. This function will accept an array of objects, where each object represents a single test case. Each test case object will contain:

  • description: A string describing the test case.
  • input: The data to be passed to the function being tested.
  • expectedOutput: The expected result for the given input.
  • testFn: A reference to the function that will be tested.

The generated tests should use describe and it blocks provided by Jest.

Key Requirements:

  1. Dynamic Suite Creation: The function should create a describe block for each unique testFn provided in the input array. The describe block's name should be derived from the testFn (e.g., its name property, or a fallback like "Unnamed Function").
  2. Dynamic Test Case Generation: Within each describe block, individual it blocks should be generated for each test case associated with that specific testFn. The it block's description should use the description property from the test case object.
  3. Assertion: Each it block should contain a Jest assertion (expect(...).toEqual(...) or expect(...).toBe(...) as appropriate) comparing the result of calling testFn with the provided input against the expectedOutput.
  4. Type Safety: The solution should be written in TypeScript, ensuring type safety for the input and generated tests.

Expected Behavior:

When the generation function is called, it should produce Jest test code that can be executed by the Jest runner. For example, if you have two different functions and several test cases for each, the output should be a structured set of describe and it blocks, correctly grouped by function.

Edge Cases:

  • Empty Input Array: The function should handle an empty array of test cases gracefully (e.g., by generating no tests).
  • Functions without Names: Handle cases where testFn might not have a readily available name.
  • Input/Output Types: While the primary focus is on generating the structure, consider how different data types for input and output might affect the assertion (though for this challenge, toEqual will generally suffice).

Examples

Let's assume we have two simple functions to test:

function add(a: number, b: number): number {
  return a + b;
}

function subtract(a: number, b: number): number {
  return a - b;
}

Example 1:

Input: [
  { description: "should return the sum of two positive numbers", input: [5, 3], expectedOutput: 8, testFn: add },
  { description: "should return the sum of a positive and a negative number", input: [5, -3], expectedOutput: 2, testFn: add }
]
Output:
describe('add', () => {
  it('should return the sum of two positive numbers', () => {
    expect(add(5, 3)).toEqual(8);
  });
  it('should return the sum of a positive and a negative number', () => {
    expect(add(5, -3)).toEqual(2);
  });
});
Explanation: Two test cases for the 'add' function are grouped under a 'describe' block named 'add'. Each test case becomes an 'it' block with its description and an assertion.

Example 2:

Input: [
  { description: "should return the difference of two positive numbers", input: [10, 4], expectedOutput: 6, testFn: subtract },
  { description: "should return a negative difference when the second number is larger", input: [4, 10], expectedOutput: -6, testFn: subtract },
  { description: "should return the sum of two positive numbers", input: [5, 3], expectedOutput: 8, testFn: add },
  { description: "should return the sum of a positive and a negative number", input: [5, -3], expectedOutput: 2, testFn: add }
]
Output:
describe('subtract', () => {
  it('should return the difference of two positive numbers', () => {
    expect(subtract(10, 4)).toEqual(6);
  });
  it('should return a negative difference when the second number is larger', () => {
    expect(subtract(4, 10)).toEqual(-6);
  });
});
describe('add', () => {
  it('should return the sum of two positive numbers', () => {
    expect(add(5, 3)).toEqual(8);
  });
  it('should return the sum of a positive and a negative number', () => {
    expect(add(5, -3)).toEqual(2);
  });
});
Explanation: Test cases for both 'add' and 'subtract' are correctly separated into their own 'describe' blocks.

Example 3: Empty Input

Input: []
Output:
(No tests generated)
Explanation: If no test cases are provided, no Jest code should be generated.

Constraints

  • The input property in the test case objects can be an array of any primitive types or simple objects. The testFn should accept arguments corresponding to the elements in this array.
  • The expectedOutput can be any primitive type or simple object.
  • The function testFn should be a JavaScript function (or a method on an object).
  • The generated tests should be valid TypeScript and Jest syntax.
  • The generation process itself should be efficient and not introduce significant overhead.

Notes

  • You'll need to import Jest's describe and it functions. For this challenge, you can assume they are globally available or import them from '@jest/globals'.
  • When deriving the describe block name from the function, use testFn.name. If testFn.name is an empty string (e.g., for anonymous arrow functions without explicit naming), provide a fallback name like "Unnamed Function".
  • The input array needs to be spread (...) as arguments when calling testFn.
  • Think about how you will represent the generated Jest code. Returning a string that contains the full Jest file content is a common approach.
Loading editor...
typescript