Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ts-mockito - Stubbing method doesn't work when argument is a custom object

In my rest architecture there is a controller (handle http request) and a service (business logic to provide data).

To test the controller I'm trying to stub the service to provide a fixed response, but I didn't understand how stub a method which require a custom object as argument (if the argument is a literal it works).

With a custom object (Farm) stubbing doesn't work because and I don't receive a Promise from service method, this is the error:

TypeError: Cannot read property 'then' of null at 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);
    });
  });
};
like image 414
gio Avatar asked Mar 18 '19 09:03

gio


1 Answers

I faced the same problem and I resolved it by mocking the method using deepEqual(), passing my custom object as a parameter.

import {deepEqual, instance, mock, when} from 'ts-mockito';
const myCustomObj = {
  userId: 123
};
when(mockedObj.myMethod(deepEqual(myCustomObj))).thenResolve(myPromise);
like image 54
Knnwulf Avatar answered Oct 21 '22 02:10

Knnwulf