I have a socket emit function which has acknowledge function as
socket.emit('ferret', 'tobi', (data) => {
const a = 5;
this.sum(a,data);
});
I want to have a test that covers the line const a = 5; and this.sum(a,data);
In order to testing easily, you should make a little refactor about your code like this:
index.ts:
import { socket } from './socket';
export const obj = {
someMethod() {
this.handleFerret = this.handleFerret.bind(this);
socket.emit('ferret', 'tobi', this.handleFerret);
},
handleFerret(data) {
const a = 5;
this.sum(a, data);
},
sum(a, b) {
//
}
};
socket.ts: simulate socket.io module, you can replace it with socket.io
export const socket = {
emit(event, data, callback) {
// real implemetation
}
};
index.spec.ts:
import { obj } from './';
import { socket } from './socket';
describe('obj', () => {
it('should emit event and execute callback correctly', () => {
const emitSpy = jest.spyOn(socket, 'emit');
obj.someMethod();
expect(emitSpy).toBeCalledWith('ferret', 'tobi', obj.handleFerret);
});
it('should handle ferret correctly', () => {
const sumSpy = jest.spyOn(obj, 'sum');
obj.handleFerret(2);
expect(sumSpy).toBeCalledWith(5, 2);
});
});
Unit test result with 100% coverage report:
PASS src/stackoverflow/57461614/index.spec.ts (12.977s)
obj
✓ should emit event and execute callback correctly (13ms)
✓ should handle ferret correctly (1ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
socket.ts | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 14.159s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57461614
Related
I am trying to write a unit test case for an async function which having for loop inside and in each iteration it calls Axios "get" method.
I am new to unit test cases, and I know how to write it for simple async function, but for the complicated case, I need some help. Thanks in advance.
export const someMethod = ({ collections }) => {
return dispatch => {
if (collections.length > 0) {
for (let index = 0; index < collections.length; index++) {
const collection = collections[index];
const { libraryUrl, bookUrl } = collection;
const containerId = Math.random().toString();
dispatch(getBook({ boolURL: bookUrl, libraryURL: libraryUrl, libraryId }));
dispatch({
type: ASSESSMENT.BOOK.ADD,
payload: { boolURL: bookUrl, libraryId }
});
}
}
};
};
Here is the solution:
actionCreators.ts:
export const ASSESSMENT = {
BOOK: {
ADD: 'ADD',
GET: 'GET'
}
};
export const getBook = data => ({ type: ASSESSMENT.BOOK.GET, payload: { data } });
export const someMethod = ({ collections }) => {
return dispatch => {
if (collections.length > 0) {
for (let index = 0; index < collections.length; index++) {
const collection = collections[index];
const { libraryUrl, bookUrl, libraryId } = collection;
const containerId = Math.random().toString();
dispatch(getBook({ boolURL: bookUrl, libraryURL: libraryUrl, libraryId }));
dispatch({
type: ASSESSMENT.BOOK.ADD,
payload: { boolURL: bookUrl, libraryId }
});
}
}
};
};
actionCreators.spec.ts:
import { someMethod, ASSESSMENT } from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
describe('someMethod', () => {
it('t1', () => {
const intialState = {};
const store = mockStore(intialState);
const collections = [
{ libraryUrl: 'aa', bookUrl: 'a', libraryId: '1' },
{ libraryUrl: 'bb', bookUrl: 'b', libraryId: '2' }
];
const expectedActions = [
{
type: ASSESSMENT.BOOK.GET,
payload: {
data: {
boolURL: collections[0].bookUrl,
libraryURL: collections[0].libraryUrl,
libraryId: collections[0].libraryId
}
}
},
{ type: ASSESSMENT.BOOK.ADD, payload: { boolURL: collections[0].bookUrl, libraryId: collections[0].libraryId } },
{
type: ASSESSMENT.BOOK.GET,
payload: {
data: {
boolURL: collections[1].bookUrl,
libraryURL: collections[1].libraryUrl,
libraryId: collections[1].libraryId
}
}
},
{ type: ASSESSMENT.BOOK.ADD, payload: { boolURL: collections[1].bookUrl, libraryId: collections[1].libraryId } }
];
store.dispatch(someMethod({ collections }));
expect(store.getActions()).toEqual(expectedActions);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58012116/actionCreators.spec.ts
someMethod
✓ t1 (6ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 100 | 100 | |
actionCreators.ts | 100 | 50 | 100 | 100 | 12 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.523s
Here is the completed demo:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58012116
I am unsure what to do? I keep getting this error. When I use an action. I am assuming my eventActions.js are not mapping to the eventReducer.js!
TypeError: Cannot read property 'id' of undefined
(anonymous function)
E:/reduxbootcamp/revents/src/features/event/eventReducer.js:67
64 | };
65 |
66 | const deleteEvent = (state, payload) => {
> 67 | return [...state.filter(event => event.id !== payload.event.id)];
68 | };
69 |
70 | export default createReducer(initialState, {
View compiled
deleteEvent
E:/reduxbootcamp/revents/src/features/event/eventReducer.js:67
64 | };
65 |
66 | const deleteEvent = (state, payload) => {
> 67 | return [...state.filter(event => event.id !== payload.event.id)];
68 | };
69 |
70 | export default createReducer(initialState, {
View compiled
(anonymous function)
E:/reduxbootcamp/revents/src/app/common/util/reducerUtils.js:5
2 | return (state = initialState, {type, payload}) => {
3 | const handler = fnMap[type];
4 |
> 5 | return handler ? handler(state, payload) : state
6 | }
7 | }
View compiled
▶ 7 stack frames were collapsed.
EventDashboard.handleDeleteEvent
E:/reduxbootcamp/revents/src/features/event/EventDashboard/EventDashboard.jsx:77
74 | };
75 |
76 | handleDeleteEvent = id => {
> 77 | this.props.deleteEvent(id);
| ^ 78 | };
79 |
80 | render() {
View compiled
onClick
E:/reduxbootcamp/revents/src/features/event/EventList/EventListItem.jsx:45
42 | <Segment clearing>
43 | <span>{event.description}</span>
44 | <Button
> 45 | onClick={() => deleteEvent(event.id)}
| ^ 46 | as="a"
47 | color="red"
48 | floated="right"
Here is my Github repo
SO far I have searched over the documentation & looked over my code for errors. Below is the code I think is responsible. Please note that I installed 5.1.1 react-redux over the newer 7+ version..
const createEvent = (state, payload) => {
return [...state, payload.event];
};
const updateEvent = (state, payload) => {
return [
...state.filter(event => event.id !== payload.event.id),
payload.event
];
};
const deleteEvent = (state, payload) => {
return [...state.filter(event => event.id !== payload.event.id)];
};
Expected the action to just work once it was all connected to the event dashboard
Your actual error is TypeError: Cannot read property 'id' of undefined and by looking at your incriminated code:
const deleteEvent = (state, payload) => {
return [...state.filter(event => event.id !== payload.event.id)];
};
...we can see that in only 2 places a property named id is accessed:
as event.id
and as payload.event.id
So either event is undefined or payload.event is.
The first scenario can happen if you have undefined values in state (which we can assume is an array). The second one usually comes from a bad use of action creators.
By looking at the stack trace we see this:
handleDeleteEvent = id => {
this.props.deleteEvent(id);
};
That means that the only thing you pass to the action creator is the id itself.
You didn’t share the implementation of createReducer but we can infer it’s some kind of boilerplate helper. Usually the only argument of action creators exposed by such helpers is directly the payload.
So, could it be that the right implementation of your reducer is the following?
const deleteEvent = (state, payload) => {
return [...state.filter(event => event.id !== payload)];
};
I am trying to test that redux actions are fired when the finish event on my upload object is fired.
Here's my epic:
const uploadFileEpic = (action$, state$, dependencies$) =>
action$.pipe(
ofType(uploadActions.UPLOAD_FILE),
mergeMap(({ payload }) => {
const { file, masterHandle } = payload;
return new Observable(o => {
const upload = masterHandle.uploadFile("/", file);
const handle = upload.handle;
upload.on("finish", () => {
o.next(
uploadActions.uploadSuccess({
masterHandle
})
);
o.complete();
});
});
})
);
Here is what I have written so far. It doesn't work:
import { of } from "rxjs";
import "rxjs/add/operator/toArray";
import { EventEmitter } from "events";
import uploadActions from "../actions/upload-actions";
import uploadEpic from "./upload-epic";
test("uploadFilesEpic filesActions.UPLOAD_FILE on success", done => {
const file = { name: "f1" };
const upload = new EventEmitter();
upload.handle = "h1";
const masterHandle = {
uploadFile: jest.fn(() => upload)
};
const action$ = of(uploadActions.uploadFile({ file, masterHandle }));
upload.emit("finish");
uploadEpic(action$).subscribe(actions => {
expect(actions).toEqual(uploadActions.uploadSuccess({ masterHandle }));
done();
});
});
It says the async callback was not fired:
FAIL src/redux/epics/upload-epic.test.js (8.531s)
✓ uploadFilesEpic filesActions.UPLOAD_FILES (9ms)
✕ uploadFilesEpic filesActions.UPLOAD_FILE on success (5021ms)
● uploadFilesEpic filesActions.UPLOAD_FILE on success
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
23 | });
24 |
> 25 | test("uploadFilesEpic filesActions.UPLOAD_FILE on success", done => {
| ^
26 | const file = { name: "f1" };
27 | const upload = new EventEmitter();
28 | upload.handle = "h1";
at new Spec (node_modules/jest-config/node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
at Object.test (src/redux/epics/upload-epic.test.js:25:1)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 9.297s
It makes sense to me that the test is failing but I'm not sure how to subscribe to the epic, fire the finish event, and then inspect the actions returned by the epic.
The event for finishing the upload is emitted too early in the test. The EventEmitter does not keep a buffer of events and if no one is subscribed, then the even is lost.
Move the emit "finish" to the bottom of the test.
upload.emit("finish"); // must emit after subscribing
I have a simple supertest test using mocha like this.
describe("test", () =>{
it("Test 1", (done) =>{
let app = (new App()).express;
supertest(app).get("/").expect(200, done);
})
})
The test runs and passes but never shuts down mocha. I tried this.
describe("test", () =>{
it("Test 1", (done) =>{
let app = (new App()).express;
supertest(app).get("/").expect(200, ()=>{
app.close();
done();
});
})
})
But app.close is not declared and I am not rolling up a whole server. How do I get mocha to stop after the test?
Here is an minimal working example:
app.js:
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.sendStatus(200);
});
const port = 3000;
const server = app.listen(port, () => {
console.info(`HTTP server is listening on http://localhost:${port}`);
});
module.exports = server;
app.test.js:
const app = require("./app");
const supertest = require("supertest");
describe("test", () => {
after((done) => {
app.close(done);
});
it("Test 1", (done) => {
supertest(app)
.get("/")
.expect(200, done);
});
});
Integration test result with coverage report:
HTTP server is listening on http://localhost:3000
test
✓ Test 1
1 passing (23ms)
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
app.js | 100 | 100 | 100 | 100 | |
app.test.js | 100 | 100 | 100 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Start the HTTP server:
☁ mocha-chai-sinon-codelab [master] ⚡ node /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/53048031/app.js
HTTP server is listening on http://localhost:3000
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/53048031
use
$ mocha --exit
or add
"exit": true
in .mocharc
source: github link
For example, assume that we have stream like following
Stream 1 | -1-2-3-1-2-3--4-----------
after debounce, I would like to have the emitted stream looks like as follows:
Stream 2 | ---------------1-2-3--4------
There are lots of examples how to debounce the stream, but they take all value as the same trigger.
The following is the example code I found in reactitve-extension website,
var Rx = require('rxjs/Rx');
var times = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 }
];
// Delay each item by time and project value;
var source = Rx.Observable.from(times)
.flatMap(function (item) {
return Rx.Observable
.of(item.value)
.delay(item.time);
})
.debounceTime(500 /* ms */);
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
The console output would be
Next: 4
Completed
But I would like to get the following output
Next: 1
Next: 2
Next: 3
Next: 4
Completed
Maxime give good answer.
I also try myself. Hope help someone who have the same question.
var Rx = require('rxjs/Rx');
var times = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 },
{ value: 5, time: 1500 }
];
// Delay each item by time and project value;
var source = Rx.Observable.from(times)
.flatMap(function (item) {
return Rx.Observable
.of(item.value)
.delay(item.time);
})
.do(obj => console.log('stream 1:', obj, 'at', Date.now() - startTime, `ms`))
.groupBy(obj => obj)
.flatMap(group => group.debounceTime(500))
let startTime = Date.now();
var subscription = source.subscribe(
function (x) {
console.log('stream 2: %s', x, 'at', Date.now() - startTime, 'ms');
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
The console will output
stream 1: 1 at 135 ms
stream 1: 2 at 206 ms
stream 1: 3 at 309 ms
stream 1: 1 at 409 ms
stream 1: 2 at 509 ms
stream 1: 3 at 607 ms
stream 1: 4 at 809 ms
stream 2: 1 at 911 ms
stream 2: 2 at 1015 ms
stream 2: 3 at 1109 ms
stream 2: 4 at 1310 ms
stream 1: 5 at 1510 ms
stream 2: 5 at 1512 ms
Completed
Here's the code I propose :
const { Observable } = Rx
const objs = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 }
];
const tick$ = Observable.interval(100)
const objs$ = Observable.from(objs).zip(tick$).map(x => x[0])
objs$
.groupBy(obj => obj.value)
.mergeMap(group$ =>
group$
.debounceTime(500))
.do(obj => console.log(obj))
.subscribe()
And the output is just as expected :
Here's a working Plunkr with demo
https://plnkr.co/edit/rEI8odCrhp7GxmlcHglx?p=preview
Explanation :
I tried to make a small schema :
The thing is, you cannot use the debounceTime directly on the main observable (that's why you only had one value). You've got to group every values in their own stream with the groupBy operator and apply the debounceTime to the splitted group of values (as I tried to show in the image). Then use flatMap or mergeMap to get one final stream.
Doc :
Here are some pages that might help you understand :
- groupBy
- debounceTime
- mergeMap