How to stub chained functions using sinon in TypeORM - sinon

I'm currently using TypeORM and Sinonjs in my project. But I'm not sure how to write the unit test in the right way. Especially how to stub a chained function call, like this
async find(id: number): Promise<User> {
const user = await this.connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: id })
.getOne();
return user;
}
My test file
it('should return a data from db', async () => {
let user = {
id: 1,
name: 'my name'
};
const getOne = Sinon.stub().resolves(user);
const where = Sinon.stub().callsArg(0);
const createQueryBuilder = Sinon.stub().callsArg(0);
const connection = {
getRepository: Sinon.stub()
};
connection.getRepository.withArgs(User).returns(createQueryBuilder);
createQueryBuilder.withArgs('user').returns(where);
where.withArgs('user.id = :id', { id: user.id }).returns(getOne);
});
I always got this error
TypeError: this.connection.getRepository(...).createQueryBuilder is not a function
Any advice is welcome!
Thank you very much!

You should use sinon.stub(obj, 'method').returnsThis() to
Causes the stub to return its this value.
E.g.
index.ts:
type User = any;
export const model = {
connection: {
getRepository(model) {
return this;
},
createQueryBuilder(model) {
return this;
},
where(query, bindings) {
return this;
},
getOne() {
console.log('get one');
}
},
async find(id: number): Promise<User> {
const user = await this.connection
.getRepository('User')
.createQueryBuilder('user')
.where('user.id = :id', { id: id })
.getOne();
return user;
}
};
index.spec.ts:
import { model } from './';
import sinon from 'sinon';
import { expect } from 'chai';
describe('model', () => {
describe('#find', () => {
afterEach(() => {
sinon.restore();
});
it('should find user', async () => {
let mUser = {
id: 1,
name: 'my name'
};
const getRepository = sinon.stub(model.connection, 'getRepository').returnsThis();
const createQueryBuilder = sinon.stub(model.connection, 'createQueryBuilder').returnsThis();
const where = sinon.stub(model.connection, 'where').returnsThis();
const getOne = sinon.stub(model.connection, 'getOne').resolves(mUser);
const user = await model.find(1);
expect(user).to.be.eql(mUser);
expect(getRepository.calledWith('User')).to.be.true;
expect(createQueryBuilder.calledWith('user')).to.be.true;
expect(where.calledWith('user.id = :id', { id: 1 })).to.be.true;
expect(getOne.calledOnce).to.be.true;
});
});
});
Unit test result with coverage report:
model
#find
✓ should find user
1 passing (13ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 86.21 | 100 | 55.56 | 84.62 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 50 | 100 | 20 | 42.86 | 6,9,12,15 |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57843415

Related

"contract" can be obtained as a result of "signAndSend", but the compilation error is not resolved

I implemented the sample code according to the polkadot.js official site in the link below.
Running and testing with "yarn dev" command works as expected.
But when I run "yarn build" I get compilation errors.
Anyone know how to solve this?
Environment
next.js
polkadot.js 9.5.1, 8.13.1, 8.4.1 #tried multiple versions
Sample Code:
const deployContract = async () => {
const { web3FromSource } = await import("#polkadot/extension-dapp");
const keyring = new Keyring({ type: 'sr25519' });
const alicePair = keyring.addFromUri('//Alice', { name: 'Alice default' });
const caller = keyring.addFromAddress("actingAddress");
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
setApi(api);
const contractWasm = contract_file.source.wasm;
const code = new CodePromise(api, abi, contractWasm);
const initValue = false;
console.log("contract is :", code);
const performingAccount = accounts[0];
const injector = await web3FromSource(performingAccount.meta.source);
const tx = code.tx.new({ gasLimit, storageDepositLimit }, initValue)
let address;
const unsub = await tx.signAndSend(alicePair, ({ contract, status }) => {
if (status.isInBlock || status.isFinalized) {
address = contract.address.toString();
unsub();
}
});
};
Compile Error:
./pages/index.tsx:64:10
Type error: Property 'contract' does not exist on type 'ISubmittableResult'.
62 | actingAddress,
63 | { signer: injector.signer },
> 64 | ({ contract, status }) => {
| ^
65 | if (status.isInBlock) {
66 | setResult("in a block");
67 | } else if (status.isFinalized) {
> Build error occurred
Thank you.

Why is this state mutated by immer produce?

I'm receiving a state mutation error when using Immer's produce to manipulate the state.
When I create a test interface manually inside the same reducer and call produce, it seems to work as expected:
export interface ItemSliceState {
items: Array<IItem> | null | undefined;
}
export const initialState: ItemSliceState = { items: [] };
export const updateItem: CaseReducer<
ItemSliceState,
PayloadAction<IItem | null | undefined>
> = (state: ItemSliceState = initialState, action) => {
const testItem: IItem = {
status: 1
};
const testItems: Array<IItems> = [testItem];
const testState: ItemSliceState = { items: testItems };
const nextTestState = produce(testState, draftState => {
if (!draftState || !draftState.items) {
return testState;
}
draftState.items[0].status = 2;
});
const nextState = produce(state, draftState => {
if (!draftState || !draftState.items) {
return state;
}
draftState.items[0].status = 2;
});
...
// testState.items[0].status = 1
// nextTestState.items[0].status = 2
// state.items[0].status = 2
// nextState.items[0].status = 2
Why is 'state' being manipulated, while 'testState' remains unchanged when produce is called in the same way?
Sandbox with state being updated correctly:
https://codesandbox.io/embed/festive-ritchie-5nf31?fontsize=14&hidenavigation=1&theme=dark
The issue here was that the api was returning testItems as a class, which cannot be mutated by immer. The solution was to send interfaces instead, which immer can mutate.

How to run Jest test on a asyn function

I have a function that will find a zip file in the directory and unzip the file and it work fine. however, I'm wondering how i can run a test on this function using Jest.
var fs = require('fs'),
PNG = require('pngjs').PNG
const unzipper = require('unzipper')
PNG = require('pngjs').PNG
const dir = __dirname + "/";
function unzipFile(fileName, outputPath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(fileName) !== true) {
reject("there is no zip file");
}
const createdFile = dir + fileName;
const stream = fs
.createReadStream(createdFile)
.pipe(unzipper.Extract({ path: outputPath }));
stream.on("finish", () => {
console.log("file unzipped");
resolve(outputPath);
});
});
}
I have read through the doc and try to use .then but I'm not sure how my expected output should be like when using Jest.
Here is the unit test solution:
index.js:
const fs = require("fs");
const unzipper = require("./unzipper");
const dir = __dirname + "/";
function unzipFile(fileName, outputPath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(fileName) !== true) {
reject("there is no zip file");
}
const createdFile = dir + fileName;
const stream = fs
.createReadStream(createdFile)
.pipe(unzipper.Extract({ path: outputPath }));
stream.on("finish", () => {
console.log("file unzipped");
resolve(outputPath);
});
});
}
module.exports = unzipFile;
index.spec.js:
const unzipFile = require("./");
const fs = require("fs");
const unzipper = require("./unzipper");
jest.mock("fs", () => {
return {
existsSync: jest.fn(),
createReadStream: jest.fn().mockReturnThis(),
pipe: jest.fn()
};
});
describe("unzipFile", () => {
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it("should unzip file correctly", async () => {
const filename = "go.pdf";
const outputPath = "workspace";
const eventHandlerMap = {};
const mStream = {
on: jest.fn().mockImplementation((event, handler) => {
eventHandlerMap[event] = handler;
})
};
const logSpy = jest.spyOn(console, "log");
fs.existsSync.mockReturnValueOnce(true);
fs.createReadStream().pipe.mockReturnValueOnce(mStream);
jest.spyOn(unzipper, "Extract").mockReturnValueOnce({});
const pending = unzipFile(filename, outputPath);
eventHandlerMap["finish"]();
const actual = await pending;
expect(actual).toEqual(outputPath);
expect(fs.existsSync).toBeCalledWith(filename);
expect(fs.createReadStream).toBeCalled();
expect(fs.createReadStream().pipe).toBeCalledWith({});
expect(logSpy).toBeCalledWith("file unzipped");
expect(mStream.on).toBeCalledWith("finish", eventHandlerMap["finish"]);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59027031/index.spec.js
unzipFile
✓ should unzip file correctly (10ms)
console.log node_modules/jest-mock/build/index.js:860
file unzipped
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 92.31 | 50 | 75 | 92.31 | |
index.js | 91.67 | 50 | 100 | 91.67 | 8 |
unzipper.js | 100 | 100 | 0 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.618s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59027031

React-Redux Testing with Jest: Received Payload = undefined

I am trying to learn/implement jest testing into my react-redux application. My test fails saying that the received does not equal what was expected, however, the actual thunk works and returns data to my application. So I've either written the test incorrectly (which i basically copy/pasted from the redux-docs) or I'm writing my thunk incorrectly.
ACTION
export const getOddGroups = () => {
return dispatch => {
return axios.get("/api/tables/oddgroups")
.then(results => {
dispatch({type: "GET_ODD_GROUPS", payload: results.data})
}).catch(err => {
dispatch({ type: "GET_ERRORS", payload: err.response.message })
})
}
}
TEST
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as oddActions from '../actions/OddActions';
import fetchMock from 'fetch-mock'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('query preview async actions', () => {
afterEach(() => {
fetchMock.restore()
})
it('creates GET_ODD_GROUPS when successful', () => {
fetchMock.get("*", {
results: { data: [{ "row1": "some data" }] },
headers: { 'content-type': 'application/json' }
})
const expectedActions = [
{ type: "GET_ODD_GROUPS", results: { data: [{ "row1": "some data" }] } },
]
const store = mockStore({ oddGroups: [] })
return store.dispatch(oddActions.getOddGroups()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
TEST RESULT OUTPUT:
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Array [
Object {
- "results": Object {
- "data": Array [
- Object {
- "row1": "some data",
- },
- ],
- },
- "type": "GET_ODD_GROUPS",
+ "payload": undefined,
+ "type": "GET_ERRORS",
},
]
EDIT - UPDATE
At the suggestion of #CoryDanielson I reworked the test using axios-mock-adapter and this post but I'm still getting the same error as above.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as oddActions from '../actions/oddActions';
import axios from "axios";
import MockAdapter from 'axios-mock-adapter';
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
let mock = new MockAdapter(axios);
describe('query preview async actions', () => {
beforeEach(function () {
/*Not sure which one is best to use in this situation yet
* will test both
*/
mock.reset(); // reset both registered mock handlers and history items with reset
//mock.restore(); //restore the original adapter (which will remove the mocking behavior)
});
it("return data for GET_ODD_GROUPS when successful", function (done) {
mock.onGet("api/tables/oddGroups")
.reply(function () {
return new Promise(function (resolve, reject) {
resolve([200, { key: 'value' }]);
});
});
const store = mockStore({ oddGroups: [] })
store.dispatch(oddActions.getOddGroups()).then(() => {
let expectedActions = [{ type: "GET_ODD_GROUPS", payload: { key: 'value' } }]
console.log(store.getActions());
expect(store.getActions()).toEqual(expectedActions);
});
setTimeout(() => {
done();
}, 1000)
});
});
LOGGING:
When I return the console state console.log(store.getActions());
Its giving me back the error dispatch action
And this console.log(store.dispatch(oddActions.getOddGroups())); returns Promise { <pending> }
FINAL SOLUTION:
After trying and failing with several options, I dropped using axios-mock-adapter and used moxios instead. After following this article I was able to successfully create tests.
Here is the solution without axios-mock-adapter, don't add too many things in your code, keep it simple. You can mock axios module manually by yourself, look at below code:
actionCreators.ts:
import axios from 'axios';
export const getOddGroups = () => {
return dispatch => {
return axios
.get('/api/tables/oddgroups')
.then(results => {
dispatch({ type: 'GET_ODD_GROUPS', payload: results.data });
})
.catch(err => {
dispatch({ type: 'GET_ERRORS', payload: err.response.message });
});
};
};
actionCreators.spec.ts:
import { getOddGroups } from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import axios from 'axios';
import { AnyAction } from 'redux';
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
jest.mock('axios', () => {
return {
get: jest.fn()
};
});
describe('actionCreators', () => {
describe('#getOddGroups', () => {
let store;
beforeEach(() => {
const initialState = {};
store = mockStore(initialState);
});
it('should get odd groups correctly', () => {
const mockedResponse = { data: 'mocked data' };
(axios.get as jest.MockedFunction<typeof axios.get>).mockResolvedValueOnce(mockedResponse);
const expectedActions = [{ type: 'GET_ODD_GROUPS', payload: mockedResponse.data }];
return store.dispatch(getOddGroups()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(axios.get).toBeCalledWith('/api/tables/oddgroups');
});
});
it('should get odd groups error', () => {
const mockedError = {
response: {
message: 'some error'
}
};
(axios.get as jest.MockedFunction<typeof axios.get>).mockRejectedValueOnce(mockedError);
const expectedActions = [{ type: 'GET_ERRORS', payload: mockedError.response.message }];
return store.dispatch(getOddGroups()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(axios.get).toBeCalledWith('/api/tables/oddgroups');
});
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/57730153/actionCreators.spec.ts
actionCreators
#getOddGroups
✓ should get odd groups correctly (5ms)
✓ should get odd groups error (2ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
actionCreators.ts | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.934s, estimated 4s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57730153

Testing async action creator (using axios) in Redux with Jest

I have an issue creating a test for my async action creator in Redux. I am using axios for fetching and Jest for testing. Data is loaded correctly in the app itself. Just the test which I am writing is going all wrong ...
I followed Redux documentation concerning testing, but it is fetchMock used there. I have trouble transferring it to my case.
I tried two approaches which are both failing:
using moxios to mock axios
using jest.mock('axios'),
This is the code in my action creator:
import {
BG_LOAD_START,
BG_LOAD_SUCCESS,
BG_LOAD_FAILURE} from './types'
import axios from 'axios'
export const bgLoad = (url, pictureNumber=0) => {
return dispatch => {
dispatch(bgLoadStart());
return axios.get(url, {
headers: {
Authorization: `Bearer ${process.env.REACT_APP_API_KEY}`
}
})
.then(res => dispatch(bgLoadSuccess(res.data.photos[pictureNumber], res.data.name, res.data.url)))
.catch(err => dispatch(bgLoadFailure(err)))
}
}
const bgLoadStart = () => {
return {
type: BG_LOAD_START
}
}
const bgLoadSuccess = (bgImage, name, url) => {
return {
type: BG_LOAD_SUCCESS,
payload: {
bgImage,
name,
url
}
}
}
const bgLoadFailure = error => {
return {
type: BG_LOAD_FAILURE,
payload: {
...error
}
}
}
and here is what I have in my test:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import {BG_LOAD_START, BG_LOAD_SUCCESS} from '../types'
import {bgLoad} from '../bgLoad'
import moxios from 'moxios'
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
// jest.mock('axios');
describe(`async bgLoad action`, ()=> {
beforeEach(()=>{
moxios.install()
})
afterEach(()=>{
moxios.uninstall()
})
it(`creates BG_LOAD_SUCCESS when data has been fetched`, ()=> {
const fetchedData = [{
image: 'this is example image',
name: 'my name is image',
url: 'this is example link'
}]
moxios.wait(()=>{
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fetchedData
})
})
// axios.get.mockResolvedValue(fetchedData);
const expectedActions = [
{type: BG_LOAD_START},
{type: BG_LOAD_SUCCESS,
payload: {
bgImage: fetchedData[0].image,
name: fetchedData[0].name,
url: fetchedData[0].url,
}
}
]
const store = mockStore({});
return store.dispatch(bgLoad()).then(()=>{
expect(store.getActions()).toEqual(expectedActions)
})
})
})
Here is the response with which I am getting from the console:
Expected value to equal:
[{"type": "BG_LOAD_START"}, {"payload": {"bgImage": "this is example image", "name": "my name is image", "url": "this is example link"}, "type": "BG_LOAD_SUCCESS"}]
Received:
[{"type": "BG_LOAD_START"}, {"payload": {}, "type": "BG_LOAD_FAILURE"}]
Here is the solution use jest.mock:
actionCreators.ts:
import { BG_LOAD_START, BG_LOAD_SUCCESS, BG_LOAD_FAILURE } from './types';
import axios from 'axios';
export const bgLoad = (url, pictureNumber = 0) => {
return dispatch => {
dispatch(bgLoadStart());
return axios
.get(url, {
headers: {
Authorization: `Bearer ${process.env.REACT_APP_API_KEY}`
}
})
.then(res => dispatch(bgLoadSuccess(res.data.photos[pictureNumber], res.data.name, res.data.url)))
.catch(err => dispatch(bgLoadFailure(err)));
};
};
const bgLoadStart = () => {
return {
type: BG_LOAD_START
};
};
const bgLoadSuccess = (bgImage, name, url) => {
return {
type: BG_LOAD_SUCCESS,
payload: {
bgImage,
name,
url
}
};
};
const bgLoadFailure = error => {
return {
type: BG_LOAD_FAILURE,
payload: {
...error
}
};
};
actionCreators.spec.ts:
import configureStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import axios from 'axios';
import { BG_LOAD_START, BG_LOAD_SUCCESS } from './types';
import { bgLoad } from './actionCreators';
import { AnyAction } from 'redux';
const middlewares = [thunk];
const mockStore = configureStore<any, ThunkDispatch<{}, any, AnyAction>>(middlewares);
jest.mock('axios', () => {
return {
get: jest.fn()
};
});
describe(`async bgLoad action`, () => {
const fetchedData = [
{
image: 'this is example image',
name: 'my name is image',
url: 'this is example link'
}
];
afterEach(() => {
jest.resetAllMocks();
});
it(`creates BG_LOAD_SUCCESS when data has been fetched`, () => {
(axios.get as jest.MockedFunction<typeof axios.get>).mockResolvedValueOnce({
data: { photos: [fetchedData[0].image], name: fetchedData[0].name, url: fetchedData[0].url }
});
const expectedActions = [
{ type: BG_LOAD_START },
{
type: BG_LOAD_SUCCESS,
payload: {
bgImage: fetchedData[0].image,
name: fetchedData[0].name,
url: fetchedData[0].url
}
}
];
const store = mockStore({});
return store.dispatch(bgLoad('https://github.com/mrdulin')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(axios.get).toBeCalledWith('https://github.com/mrdulin', {
headers: {
Authorization: `Bearer ${process.env.REACT_APP_API_KEY}`
}
});
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/55966274/actionCreators.spec.ts
async bgLoad action
✓ creates BG_LOAD_SUCCESS when data has been fetched (20ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 88.89 | 50 | 71.43 | 88.24 | |
actionCreators.ts | 86.67 | 50 | 71.43 | 85.71 | 15,37 |
types.ts | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.774s, estimated 6s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/55966274

Resources