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);
});
});
};
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With