First, let’s say we have the following code structure:

// src/boundaries/LanguageModelService.ts
export interface LanguageModelService {
  getLanguageModel: () => void;
};

// src/services/MyLanguageModelService.ts
import { LanguageModelService } from '../boundaries/LanguageModelService';

export const myLanguageModelService: LanguageModelService = {
  getLanguageModel: () => {
    console.log('log!');
  },
};

You can use a simple util function called getCoreMethodName to get the method name in a typesafe way when testing.

// src/utils/getCoreMethodName.ts
export const getCoreMethodName = <T>(method: keyof T) => method;

Now, when we test the getLanguageModel method in MyLanguageModelService, we can use the getCoreMethodName function to get the method name in a typesafe way.

// src/services/MyLanguageModelService.test.ts
import { myLanguageModelService } from './MyLanguageModelService';
import { getCoreMethodName } from '../utils/getCoreMethodName';

const mn = getCoreMethodName<LanguageModelService>;

describe(mn('getLanguageModel'), () => {
  it('should log something', () => {
    const spy = jest.spyOn(console, 'log');

    myLanguageModelService.getLanguageModel();

    expect(spy).toHaveBeenCalledWith('log!');
  });
});

This works great because if we change the method name in LanguageModelService, the test will fail to compile, and we’ll know we need to update the test. This is a great way to ensure that your tests stay in sync with your code.