In Nestjs, I try to mock a ClientProxy but I receive a typing error telling me that I need to define all properties of ClientProxy class. Of course, I don't want to mock every property of this Class.
Any idea on how to solve this?
Here's a simplified piece of code:
service.ts
#Injectable()
export class SimulationsService {
constructor() {}
#Client({
transport: Transport.NATS,
options: {
url: env.nats,
user: env.usernats,
pass: env.passnats,
},
})
simulationClient: ClientProxy;
method1(){
this.simulationClient.send('simulatePattern', simulation)
.pipe(
map(sim=>{
//SOME CODE
return sim;
})
});
}
service.spec.ts
describe('method1', () => {
it('should return a processed Simulation', async () => {
jest.spyOn(
service,
'simulationClient',
'get',
).mockImplementation(()=>{
return {send() {
return of(simulationMock as Simulation);
}}
});
});
expect(await service.update('test', unfedSimulationMock)).toEqual(simulationMock);
});
});
Error output :
Type '{ send(): Observable; }' is missing the following properties from type 'ClientProxy': connect, close, routingMap, emit, and 7 more.
Here is a solution:
export interface ClientProxy {
send(pattern: string, simulation: any): any;
connect(): any;
close(): any;
routingMap(): any;
}
I simplify the code for service.ts:
// tslint:disable-next-line: interface-name
export interface ClientProxy {
send(pattern: string, simulation: any): any;
connect(): any;
close(): any;
routingMap(): any;
}
export class SimulationsService {
constructor(private simulationClient: ClientProxy) {}
public method1() {
const simulation = {};
this.simulationClient.send('simulatePattern', simulation);
}
}
Unit test, partial mock ClientProxy which means we can mock specific methods without the type error.
import { SimulationsService, ClientProxy } from './service';
import { mock } from '../../__utils';
const simulationClientMock = mock<ClientProxy>('send');
// Argument of type '{ send: Mock<any, any>; }' is not assignable to parameter of type 'ClientProxy'.
// Type '{ send: Mock<any, any>; }' is missing the following properties from type 'ClientProxy': connect, close, routingMap
// const simulationClientMock = {
// send: jest.fn()
// };
const service = new SimulationsService(simulationClientMock);
describe('SimulationsService', () => {
describe('#method1', () => {
it('t1', () => {
simulationClientMock.send.mockReturnValueOnce('mocked data');
service.method1();
expect(simulationClientMock.send).toBeCalledWith('simulatePattern', {});
});
});
});
__util.ts:
type GenericFunction = (...args: any[]) => any;
type PickByTypeKeyFilter<T, C> = {
[K in keyof T]: T[K] extends C ? K : never;
};
type KeysByType<T, C> = PickByTypeKeyFilter<T, C>[keyof T];
type ValuesByType<T, C> = {
[K in keyof T]: T[K] extends C ? T[K] : never;
};
type PickByType<T, C> = Pick<ValuesByType<T, C>, KeysByType<T, C>>;
type MethodsOf<T> = KeysByType<Required<T>, GenericFunction>;
type InterfaceOf<T> = PickByType<T, GenericFunction>;
type PartiallyMockedInterfaceOf<T> = {
[K in MethodsOf<T>]?: jest.Mock<InterfaceOf<T>[K]>;
};
export function mock<T>(...mockedMethods: Array<MethodsOf<T>>): jest.Mocked<T> {
const partiallyMocked: PartiallyMockedInterfaceOf<T> = {};
mockedMethods.forEach(mockedMethod => (partiallyMocked[mockedMethod] = jest.fn()));
return partiallyMocked as jest.Mocked<T>;
}
Unit test result with 100% coverage:
PASS src/stackoverflow/57960039/service.spec.ts
SimulationsService
#method1
✓ t1 (4ms)
----------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
__utils.ts | 100 | 100 | 100 | 100 | |
src/stackoverflow/57960039 | 100 | 100 | 100 | 100 | |
service.ts | 100 | 100 | 100 | 100 | |
----------------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.234s, estimated 6s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57960039
Related
I have a soap RestAPI to call for excecuting a function from a service for which i have used soap lib with async await. The code is working fine. When comes to unit testing the test case fails at the callback method returning from the client. The code and UT error follows.
Function to call Soap Client - Code - Helper.ts
class soapHelper {
public async registerInSoap(
uploadRequest: ImportExperimentRequestDto,
): Promise<{ RegisterExperimentResult: IffManPublishResponseDto }> {
const url = "http://test.com/Services/Req.svc?wsdl";
const client = await soap.createClientAsync(flavourUrl);
return client.RegisterExperimentAsync({ upload: uploadRequest});
}
}
Test Case - Code
describe("**** Register via soap service ****", () => {
it("** should excecute register method **", async () => {
const request = cloneDeep(MOCK.API_PAYLOAD);
const clientResponse = {
RegisterExperimentAsync: jest.fn(),
};
jest.spyOn<any, any>(soap, "createClientAsync").mockReturnValueOnce(() => Promise.resolve(clientResponse));
const result= await soapHelper.registerInSoap(request);
expect(result).toEqual({ Result: AFB_MOCK_RESPONSE.API_RESPONSE });
});
});
Error
TypeError: client.RegisterExperimentAsync is not a function
enter image description here
The mock resolved value of soap.createClientAsync() should be a soap client.
E.g.
index.ts:
import * as soap from 'soap';
interface ImportExperimentRequestDto {}
interface IffManPublishResponseDto {}
export class SoapHelper {
public async registerInSoap(
uploadRequest: ImportExperimentRequestDto,
): Promise<{ RegisterExperimentResult: IffManPublishResponseDto }> {
const url = 'http://test.com/Services/Req.svc?wsdl';
const client = await soap.createClientAsync(url);
return client.RegisterExperimentAsync({ upload: uploadRequest });
}
}
index.test.ts:
import { Client } from 'soap';
import * as soap from 'soap';
import { SoapHelper } from '.';
describe('**** Register via soap service ****', () => {
it('** should excecute register method **', async () => {
const request = {};
const mClient = ({
RegisterExperimentAsync: jest.fn().mockResolvedValueOnce({ Result: 'mock result' }),
} as unknown) as Client;
jest.spyOn(soap, 'createClientAsync').mockResolvedValue(mClient);
const soapHelper = new SoapHelper();
const result = await soapHelper.registerInSoap(request);
expect(result).toEqual({Result: 'mock result'});
});
});
Test result:
PASS stackoverflow/72604110/index.test.ts (9.87 s)
**** Register via soap service ****
✓ ** should excecute register method ** (3 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.471 s
I have a controller Acounts with a method sum, and a service file named validation with an object register. This object has a method validate that validates the form provided and returns a Boolean.
Controller
sum: function(req, res) {
//validate form with a validation service function
const validator = new validation.register();
let check = validator.validate(req.body.form);
let count = 0;
if (check) {
count += 1;
} else {
count -= 1;
}
res.send(count);
},
test
//imports
const chai = require("chai");
const expect = chai.expect;
const sinon = require("sinon");
const util = require('util'); // to print complex objects
const acountsC = require("../../../api/controllers/AcountsController.js");
describe("AcountsController", function() {
describe("sum", function() {
let req = {
body: {
form: {}
}
}
let res = {
send: sinon.spy()
}
let validation = {
register: {
validate: function() {}
}
}
let stub_validate = sinon.stub(validation.register, "validate").returns(true);
it("count should be 1 when validation is true", function() {
acountsC.sum(req, res);
expect(count).to.equal(1);
});
});
});
test log
AcountsController
sum
1) count should be 1 when validation is true
0 passing (5s)
1 failing
1) AcountsController
sum
count should be 1 when validation is true:
ReferenceError: count is not defined
note
I understand that the test is supposed to execute the piece of code we are calling, while replacing the external functions called in that piece of code(The controller) making it return whatever we set. If the test is executing that piece of code, why I can't access any variables created in the controller?
I've tried by spying res.send(), and check if it was called with a 1. I didn't succeed.
I searched everywhere how to perform an assertion to a variable, but I found nothing. :(
hope you can help
Here is the unit test solution:
accountController.js:
const validation = require('./validation');
class AccountController {
sum(req, res) {
const validator = new validation.register();
const check = validator.validate(req.body.form);
let count = 0;
if (check) {
count += 1;
} else {
count -= 1;
}
res.send(count);
}
}
module.exports = AccountController;
validation.js:
class Register {
validate() {}
}
module.exports = {
register: Register,
};
accountController.test.js:
const AccountController = require('./accountController');
const sinon = require('sinon');
const validation = require('./validation');
describe('60182912', () => {
afterEach(() => {
sinon.restore();
});
describe('#sum', () => {
it('should increase count and send', () => {
const registerInstanceStub = {
validate: sinon.stub().returns(true),
};
const registerStub = sinon.stub(validation, 'register').callsFake(() => registerInstanceStub);
const accountController = new AccountController();
const mRes = { send: sinon.stub() };
const mReq = { body: { form: {} } };
accountController.sum(mReq, mRes);
sinon.assert.calledWithExactly(mRes.send, 1);
sinon.assert.calledOnce(registerStub);
sinon.assert.calledWithExactly(registerInstanceStub.validate, {});
});
it('should decrease count and send', () => {
const registerInstanceStub = {
validate: sinon.stub().returns(false),
};
const registerStub = sinon.stub(validation, 'register').callsFake(() => registerInstanceStub);
const accountController = new AccountController();
const mRes = { send: sinon.stub() };
const mReq = { body: { form: {} } };
accountController.sum(mReq, mRes);
sinon.assert.calledWithExactly(mRes.send, -1);
sinon.assert.calledOnce(registerStub);
sinon.assert.calledWithExactly(registerInstanceStub.validate, {});
});
});
});
Unit test results with coverage report:
60182912
#sum
✓ should increase count and send
✓ should decrease count and send
2 passing (10ms)
----------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 50 | 100 |
accountController.js | 100 | 100 | 100 | 100 |
validation.js | 100 | 100 | 0 | 100 |
----------------------|---------|----------|---------|---------|-------------------
source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/60182912
The problem was the lifecycle file I created trusting in sails docs. That documentation is for integrated testing due to the fact that it lifts sails before any other test. This is quite slow while unit tests should be fast. Erasing that file was enough to test the controller successfully. Otherwise sails messes up with the tests in a way I don't even fully understand. I suppose it is due to sails making services globally available. So when my controller calls for validation service, this one returns some default and not what the stub says it should return.
UPDATE:
I managed to make it work. When lifting sails before testing, only the tested controller should be require, services and models shouldn't.
I am not able to write test case for below code base, I could able to stub mock data as shown below but its failing at fileData.on
const bucketStub = sinon.stub(Storage.prototype, "bucket").callsFake(() => {
return {
file: fileStub,
createReadStream: createReadStreamStub,
} as any;
});
Below is code for which I am trying to write test cases using mocha chai and sinon
function abc(req, res){
const bucketName = "abc-xyz"
const fileName = "Sample.json"
var file = storage.bucket(bucketName).file(fileName);
const myfile = file.createReadStream();
var buffer = '';
myfile.on('data', function(a) {
buffer += a;
}).on('end', function() {
console.log(buffer)
res.status(200).send(buffer)
});
}
Here is the unit test solution:
index.ts:
import { Storage } from "#google-cloud/storage";
const storage = new Storage();
export function abc(req, res) {
const bucketName = "abc-xyz";
const fileName = "Sample.json";
const file = storage.bucket(bucketName).file(fileName);
const myfile = file.createReadStream();
let buffer = "";
myfile
.on("data", function(a) {
buffer += a;
})
.on("end", function() {
console.log(buffer);
res.send(buffer);
});
}
index.spec.ts:
import { abc } from ".";
import sinon from "sinon";
import { Storage } from "#google-cloud/storage";
describe("59405183", () => {
beforeEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should pass", () => {
const mReq = {};
const mRes = {
send: sinon.stub(),
};
const onStub = sinon.stub();
const onDataStub = onStub
.withArgs("data")
.yields("sinon")
.returnsThis();
const onEndStub = onStub.withArgs("end").yields();
const readStreamStub = { on: onStub };
const stubs = {
file: sinon.stub().returnsThis(),
createReadStream: sinon.stub().returns(readStreamStub),
};
const bucketStub = sinon.stub(Storage.prototype, "bucket").callsFake(() => stubs as any);
abc(mReq, mRes);
sinon.assert.calledWith(bucketStub, "abc-xyz");
sinon.assert.calledWith(stubs.file, "Sample.json");
sinon.assert.calledOnce(stubs.createReadStream);
sinon.assert.calledWith(onDataStub, "data", sinon.match.func);
sinon.assert.calledWith(onEndStub, "end", sinon.match.func);
sinon.assert.calledWith(mRes.send, "sinon");
});
});
Unit test result with 100% coverage:
59405183
sinon
✓ should pass
1 passing (28ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59405183%2659497269
Can anyone help me in writing one sample test scenarios?
storage is a library (google Cloud) finally below the line of code will return me an array consist of filename and Date.
function abc(){
const files = [];
files = await storage.bucket(bucketName).getFiles();
return files;
}
Here is the unit test solution:
index.ts:
import { Storage } from "#google-cloud/storage";
const storage = new Storage();
export async function abc() {
const bucketName = "xxx-dev";
const files = await storage.bucket(bucketName).getFiles();
return files;
}
export async function xyz(res) {
const bucketName = "xxx-dev";
return storage
.bucket(bucketName)
.file(res.fileName)
.createReadStream();
}
index.spec.ts:
import { abc, xyz } from "./";
import { Storage } from "#google-cloud/storage";
import sinon from "sinon";
import { expect } from "chai";
describe("59373281", () => {
afterEach(() => {
sinon.restore();
});
it("abc should pass", async () => {
const getFilesStub = sinon.stub().resolves(["file1", "file2"]);
const bucketStub = sinon.stub(Storage.prototype, "bucket").callsFake(() => {
return { getFiles: getFilesStub } as any;
});
const actual = await abc();
expect(actual).to.be.deep.eq(["file1", "file2"]);
sinon.assert.calledWith(bucketStub, "xxx-dev");
sinon.assert.calledOnce(getFilesStub);
});
it("xyz should pass", async () => {
const fileStub = sinon.stub().returnsThis();
const createReadStreamStub = sinon.stub();
const bucketStub = sinon.stub(Storage.prototype, "bucket").callsFake(() => {
return {
file: fileStub,
createReadStream: createReadStreamStub,
} as any;
});
const mRes = { fileName: "jestjs.pdf" };
await xyz(mRes);
sinon.assert.calledWith(bucketStub, "xxx-dev");
sinon.assert.calledWith(fileStub, "jestjs.pdf");
sinon.assert.calledOnce(createReadStreamStub);
});
});
Unit test result with 100% coverage:
59373281
✓ abc should pass
✓ xyz should pass
2 passing (46ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
package versions:
"#google-cloud/storage": "^4.1.3",
"sinon": "^7.5.0",
"mocha": "^6.2.2",
"chai": "^4.2.0",
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59373281
I was trying to test for below code , I thought i will co relate but still getting error as file.on not functions :-(
function abc(req, res){
const bucketName = "abc-xyz"
const fileName = "Sample.json"
var file = storage.bucket(bucketName).file(fileName);
const myfile = file.createReadStream();
var buffer = '';
myfile.on('data', function(a) {
buffer += a;
}).on('end', function() {
console.log(buffer)
res.send(buffer)
});
}
I wrote the following code in node.js:
const rp = require('request-promise');
export async function readSite() {
try {
let response = await rp('http://www.google.com');
return response;
}
catch(err) {
console.log(err);
}
}
export async function main() {
let response = await readSite();
return response;
}
I want to test main method. Since readSite is an asynchronous method that I don't want to run during the test, I want to mock/stub it, meaning whenever the test call readSite method it will automatically get a response (without calling an external website).
const sinon = require('sinon');
const app = require("./app");
describe('when there was no ingredient', async() => {
it('mama would be angry', async () => {
sinon.stub(app, 'readSite').returns(Promise.resolve('blabla'));
let res = await app.main();
console.log("*************************************", res);
})
})
When I run the test (run mocha ./test.js in terminal), I see that "www.google.com" has been read, and it means that stub has not succeeded.
What am I doing wrong?
Here is the solution:
index.js:
const rp = require("request-promise");
async function readSite() {
try {
let response = await rp("http://www.google.com");
return response;
} catch (err) {
console.log(err);
}
}
async function main() {
let response = await exports.readSite();
return response;
}
exports.readSite = readSite;
exports.main = main;
index.spec.js:
const sinon = require("sinon");
const { expect } = require("chai");
const app = require(".");
describe("main", () => {
it("should stub readSite", async () => {
const readSiteStub = sinon.stub(app, "readSite").resolves("blabla");
const actual = await app.main();
expect(actual).to.be.equal("blabla");
readSiteStub.restore();
});
});
Unit test result with coverage report:
main
✓ should stub readSite
1 passing (9ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 77.78 | 100 | 75 | 77.78 | |
index.js | 55.56 | 100 | 50 | 55.56 | 4,5,6,8 |
index.spec.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59068172
For more info, see: https://github.com/facebook/jest/issues/936#issuecomment-214939935