метод ts-mockito — Stubbing не работает, когда аргументом является пользовательский объект

#typescript #unit-testing #mocha.js #sinon

#typescript #модульное тестирование #mocha.js #sinon

Вопрос:

В моей архитектуре rest есть контроллер (обрабатывает http-запрос) и сервис (бизнес-логика для предоставления данных).

Для тестирования контроллера я пытаюсь заглушить службу, чтобы обеспечить фиксированный ответ, но я не понял, как заглушить метод, который требует пользовательского объекта в качестве аргумента (если аргумент является литералом, он работает).

С пользовательским объектом (ферма) заглушение не работает, потому что я не получаю обещание от метода service, это ошибка:

Ошибка типа: не удается прочитать свойство ‘then’ с нулевым значением в FarmsController.createFarm (/Users/giovannimarino/Projects/rt-cloud/services/farms/src/farms/farms.controller.ts:17:17)

farms.controller.spec.ts

 describe('FarmsController', () => {
  const farmsServiceMock: FarmsService = mock(FarmsService);
  let controller: FarmsController;

  interface TestData {
    farm: Farm;
  }
  let testData: TestData;

  beforeEach(() => {
    reset(farmsServiceMock);
    const farmsServiceMockInstance: FarmsService = instance(farmsServiceMock);
    controller = new FarmsController(farmsServiceMockInstance);

    testData = {
      farm: <Farm> {
        name: 'CattD',
        imageUrl: 'img/farm-123b341.png',
        lang: 'en',
      }
    };
  });

  describe('createFarm function', () => {
    describe('success', () => {
      it('should return HTTP 200 OK', async () => {
        when(farmsServiceMock.createFarm(testData.farm)).thenReturn(Promise.resolve<Farm>(testData.farm));
        const pathParameters: PathParameter = {
          name: 'CattD',
        };
        const bodyRequest: Body = {
          name: testData.farm.name,
          imageUrl: testData.farm.imageUrl,
          lang: testData.farm.lang
        };
        const response: ApiResponseParsed<Farm> = await callSuccess<Farm>(controller.createFarm, pathParameters, bodyRequest);
        expect(response.statusCode).to.equal(HttpStatusCode.Ok);
      });
    });
  });
});
  

farm.controller.ts

 export class FarmsController {
  public constructor(private readonly _service: FarmsService) {
  }

  public createFarm: ApiHandler = (event: ApiEvent, context: ApiContext, callback: ApiCallback): void => {
    if (!event.body) {
      throw new Error('Empty input');
    }
    const input: Farm = <Farm> JSON.parse(event.body);

    this._service.createFarm(input)
      .then((data: Farm) => {
        return ResponseBuilder.created(data, callback);  // tslint:disable-line arrow-return-shorthand
      })
      .catch((error: ErrorResult) => {
        if (error instanceof NotFoundResult) {
          return ResponseBuilder.notFound(error.code, error.description, callback);
        }

        if (error instanceof ForbiddenResult) {
          return ResponseBuilder.forbidden(error.code, error.description, callback);
        }

        return ResponseBuilder.internalServerError(error, callback);
      });
  }
}
  

farm.service.ts

 export class FarmsService {
  public constructor(private readonly _repo: FarmsRepository) {
  }

  public async createFarm(farm: Farm): Promise<Farm> {
    try {
      return this._repo.create(farm);
    } catch (error) {
      throw error;
    }
  }
}
  

callSuccess

 export const callSuccess: SuccessCaller = <T>(handler: ApiHandler, 

pathParameters?: PathParameter, body?: Body): Promise<ApiResponseParsed<T>> => {
  // tslint:disable-next-line typedef (Well-known constructor.)
  return new Promise((resolve, reject) => {
    const event: ApiEvent = <ApiEvent> {};
    if (pathParameters) {
      event.pathParameters = pathParameters;
    }
    if (body) {
      event.body = JSON.stringify(body);
    }

    handler(event, <ApiContext> {}, (error?: Error | null | string, result?: ApiResponse): void => {
      if (typeof result === 'undefined') {
        reject('No result was returned by the handler!');
        return;
      }

      const parsedResult: ApiResponseParsed<T> = result as ApiResponseParsed<T>;
      parsedResult.parsedBody = JSON.parse(result.body) as T;
      resolve(parsedResult);
    });
  });
};
  

Комментарии:

1. Что вы используете для заглушки? Я не знаком с when / thenReturn синтаксисом в вашем тесте.

2. github.com/NagRock/ts-mockito

Ответ №1:

Я столкнулся с той же проблемой и решил ее, подделав метод с помощью deepEqual() , передав свой пользовательский объект в качестве параметра.

 import {deepEqual, instance, mock, when} from 'ts-mockito';
  
 const myCustomObj = {
  userId: 123
};
when(mockedObj.myMethod(deepEqual(myCustomObj))).thenResolve(myPromise);
  

Комментарии:

1. Да, по умолчанию ts-mockito используется strictEqual . Поэтому, если вы хотите проверить содержимое объектов, используйте deepEqual , как предложил @Knnwulf

2. @NagRock: это где-нибудь задокументировано? Или il может быть задокументирован здесь ? Вы бы просмотрели и приняли PR?