RxJs Marble Test Fails When Using Observable.fromPromise - unit-testing

I have been attempting to write an RxJS marble test for a simple redux-observable epic but cannot get it to pass. It appears that using fromPromise in the observable chain under test does not emit the items per the expected marble sequence when flush is called on the testScheduler.
Providing a sample of the test. If I replace Observable.fromPromise to Observable.of the test will pass.
Any insight is appreciated. RxJS 5 / redux-observable 0.18
...
const MY_ACTION = 'MY_ACTION';
const myAction = () => ({
type: MY_ACTION,
payload: {test: 'testval'},
});
const epic = action$ =>
action$.ofType(MY_ACTION).switchMap(() =>
Observable.concat(
Observable.of({type: 'test1'}),
Observable.fromPromise(Promise.resolve({type: 'test2'})),
)
);
it('it should work', () => {
const deepEquals = (actual, expected) => {
expect(actual).to.deep.equal(expected);
};
const createTestScheduler = () =>
new TestScheduler(deepEquals);
const marbles1 = '-a-';
const marbles2 = '-(bc)-';
const values = {
a: myAction(),
b: {type: 'test1'},
c: {type: 'test2'},
};
const ts = createTestScheduler();
const source = ActionsObservable.from(
ts.createColdObservable(marbles1, values)
);
const actual = epic(source);
ts.expectObservable(actual);
ts.expectObservable(actual).toBe(marbles2, values);
ts.flush();
});
...

Take a look at the documentation under the 'Known issues' section: https://rxjs-dev.firebaseapp.com/guide/testing/marble-testing
You can use the from operator to wrap a promise like so:
const myAsyncCode = () => from(Promise.resolve('something'));
it('has async code', (done) => {
myAsyncCode().subscribe((d) => {
assertEqual(d, 'something');
done();
});
});

Related

How to write a unit test for CronJob in Nestjs

I am facing difficulty writing a unit test in jest for the code snippet below:
async addCronJob(props: IAddAndUpdateCronJobDetails) {
const {name, individualSchedule} = props;
const parsedCronTime = convertDateAndTimeToCron(
individualSchedule.timeOfRun,
individualSchedule.dateOfrun
)
const {jobType, dateOfRun, id, timeOfRun} = individualSchedule;
const newJob = new CronJob(
parsedCronTime,
async () => {
return this.sqsService.getSqsApproval({
//some properties
}).then(() => {
//some logic
})
},
null,
false,
'Asia/Singapore'
)
this.schedulerRegistry.addCronJob(name, newJob)
newJob.start()
}
And here is my unit test:
//at the top
jest.mock('cron', () => {
const mScheduleJob = {start: jest.fn(), stop: jest.fn()};
const mCronJob = jest.fn(() => mScheduleJob);
return {CronJob: mCronJob}
})
***************
describe('addCronJob', () => {
it('should add a new cron job', async (done) => {
const testFn = jest.fn();
const parsedCronTime = convertDateAndTimeToCron(
mockSchedule.timeOfRun,
mockSchedule.dateOfrun
)
const testCronJob = new CronJob(
parsedCronTime,
testFn,
null,
false,
'Asia/Singapore'
);
return dynamicCronService.addCron({//properties}).then(() => {
expect(CronJob).toHaveBeenCalledWith(//properties);
expect(testCronJob.start).toBeCalledTimes(1);
done()
})
})
})
The above test passes without error. However, it is unable to test for this block of async code within the cron job itself:
async () => {
return this.sqsService.getSqsApproval({
//some properties
}).then(() => {
//some logic
})
}
Anyone have an idea how to test the above block of code?
Thanks!
Probably late to the party, but I struggled with this myself and wanted to share my solution:
Method in service
async addCronJob(taskName: string, cronEx: string, onTickCallback: () => void | Promise<void>): Promise<void> {
const newJob = new CronJob(cronEx, onTickCallback);
this.schedulerRegistry.addCronJob(taskName, newJob);
newJob.start();
}
Test
it('should create cronJob', async () => {
await service.addCronJob(jobName, testCronExpression, callbackFunction);
expect(schedulerRegistryMock.addCronJob).toHaveBeenCalledWith(jobName, expect.any(CronJob));
jest.advanceTimersByTime(60 * 60 * 1000);
expect(callbackFunction).toHaveBeenCalled();
});
Instead of creating a test cronjob with a test function, I had to mock the actual function I'm expecting the cronjob to call on tick (in your case, that should be getSqsApproval I believe). Then I expected schedulerRegistry.addCronJob to be called with any CronJob, since I can't know the specific job. Creating a new job and expecting it here won't work.
Finally, I advanced the time by 1 hour because my testCronExpression was 0 * * * *. You should advance the time depending on the cron expression you use for testing.
Expecting the callbackFunction to have been called after the time passed (virtually) worked for me.
Hope this helps!

mocking admin.firestore() and admin.firestore.FieldValue.serverTimestamp()

I've been trying to mock
admin.firestore.FieldValue.serverTimestamp().
However once I've
mocked admin.firestore(), all the calls just hit that stub instead.
So the error runnning the test is... Trying to stub property 'serverTimestamp' of undefined
const admin = require("firebase-admin")
const test = require("firebase-functions-test")()
describe("Unit test", () => {
let allFunctions, adminFirestoreStub
beforeAll(() => {
adminFirestoreStub = sinon.stub(admin, "initializeApp")
allFunctions = require("../index")
})
describe("Main", () => {
it("Test", async () => {
const dbStub = sinon.stub()
const collStub = sinon.stub()
sinon.stub(admin, "firestore").get(() => dbStub)
dbStub.returns({
collection: collStub,
})
sinon.stub(admin.firestore.FieldValue, "serverTimestamp").returns(Date())
const wrapped = test.wrap(allFunctions.my_func)
await wrapped(data, context)
})
})
})
Thanks Doug, put a const to serverTimestamp() above the actual function and it worked.

How to spy on properties returned from mocked file

Using Jest, I would like to mock one of my dependencies, but I would like to spy on the properties that mock is returning. For example, something like:
const mockGet = jest.fn(() => {});
const mockPost = jest.fn(() => {});
const mockDel = jest.fn(() => {});
jest.mock('../../services/MyService', () => {
return {
mockGet,
mockPost,
mockDel
};
});
// ...
describe('MyService', () => {
it('should do something', () => {
myService.doThis();
expect(mockGet).toHaveBeenCalled();
});
});
The problem is this gives an error jest.mock() is not allowed to reference any out-of-scope variables.
The only way I can get it to work so far is by defining the mocks inside the call to jest.mock:
jest.mock('../../services/MyService', () => {
return {
mockGet: jest.fn(() => {}),
mockPost: jest.fn(() => {}),
mockDel: jest.fn(() => {})
};
});
But then how do I access the properties to see if they were called?
Here is the solution:
Service.ts:
class Service {
public get() {
return 'real data';
}
public post() {}
public del() {}
public doThis() {
console.log('real do this');
return this.get();
}
}
export { Service };
Service.spec.ts:
import { Service } from './service';
const service = new Service();
// console.log(service);
jest.mock('./service.ts', () => {
const originalModule = jest.requireActual('./service.ts');
const { Service: OriginalService } = originalModule;
const ServiceMocked = {
...OriginalService.prototype,
get: jest.fn(),
post: jest.fn(),
del: jest.fn()
};
return {
Service: jest.fn(() => ServiceMocked)
};
});
describe('Service', () => {
it('should do something', () => {
service.get = jest.fn().mockReturnValueOnce('mocked data');
const actualValue = service.doThis();
expect(actualValue).toBe('mocked data');
expect(service.get).toHaveBeenCalled();
});
});
Unit test result:
PASS src/stackoverflow/57172774/service.spec.ts
Service
✓ should do something (11ms)
console.log src/stackoverflow/57172774/service.ts:6
real do this
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.336s, estimated 3s

Jest mocking / spying on Mongoose chained (find, sort, limit, skip) methods

What I am looking to do:
spy on the method calls chained onto find() used in a static Model method definition
chained methods: sort(), limit(), skip()
Sample call
goal: to spy on the arguments passed to each of the methods in a static Model method definition:
... static method def
const results = await this.find({}).sort({}).limit().skip();
... static method def
what did find() receive as args: completed with findSpy
what did sort() receive as args: incomplete
what did limit() receive as args: incomplete
what did skip() receive as args: incomplete
What I have tried:
the mockingoose library but it is limited to just find()
I have been able to successfully mock the find() method itself but not the chained calls that come after it
const findSpy = jest.spyOn(models.ModelName, 'find');
researching for mocking chained method calls without success
I was not able to find a solution anywhere. Here is how I ended up solving this. YMMV and if you know of a better way please let me know!
To give some context this is part of a REST implementation of the Medium.com API I am working on as a side project.
How I mocked them
I had each chained method mocked and designed to return the Model mock object itself so that it could access the next method in the chain.
The last method in the chain (skip) was designed to return the result.
In the tests themselves I used the Jest mockImplementation() method to design its behavior for each test
All of these could then be spied on using expect(StoryMock.chainedMethod).toBeCalled[With]()
const StoryMock = {
getLatestStories, // to be tested
addPagination: jest.fn(), // already tested, can mock
find: jest.fn(() => StoryMock),
sort: jest.fn(() => StoryMock),
limit: jest.fn(() => StoryMock),
skip: jest.fn(() => []),
};
Static method definition to be tested
/**
* Gets the latest published stories
* - uses limit, currentPage pagination
* - sorted by descending order of publish date
* #param {object} paginationQuery pagination query string params
* #param {number} paginationQuery.limit [10] pagination limit
* #param {number} paginationQuery.currentPage [0] pagination current page
* #returns {object} { stories, pagination } paginated output using Story.addPagination
*/
async function getLatestStories(paginationQuery) {
const { limit = 10, currentPage = 0 } = paginationQuery;
// limit to max of 20 results per page
const limitBy = Math.min(limit, 20);
const skipBy = limitBy * currentPage;
const latestStories = await this
.find({ published: true, parent: null }) // only published stories
.sort({ publishedAt: -1 }) // publish date descending
.limit(limitBy)
.skip(skipBy);
const stories = await Promise.all(latestStories.map(story => story.toResponseShape()));
return this.addPagination({ output: { stories }, limit: limitBy, currentPage });
}
Full Jest tests to see implementation of the mock
const { mocks } = require('../../../../test-utils');
const { getLatestStories } = require('../story-static-queries');
const StoryMock = {
getLatestStories, // to be tested
addPagination: jest.fn(), // already tested, can mock
find: jest.fn(() => StoryMock),
sort: jest.fn(() => StoryMock),
limit: jest.fn(() => StoryMock),
skip: jest.fn(() => []),
};
const storyInstanceMock = (options) => Object.assign(
mocks.storyMock({ ...options }),
{ toResponseShape() { return this; } }, // already tested, can mock
);
describe('Story static query methods', () => {
describe('getLatestStories(): gets the latest published stories', () => {
const stories = Array(20).fill().map(() => storyInstanceMock({}));
describe('no query pagination params: uses default values for limit and currentPage', () => {
const defaultLimit = 10;
const defaultCurrentPage = 0;
const expectedStories = stories.slice(0, defaultLimit);
// define the return value at end of query chain
StoryMock.skip.mockImplementation(() => expectedStories);
// spy on the Story instance toResponseShape() to ensure it is called
const storyToResponseShapeSpy = jest.spyOn(stories[0], 'toResponseShape');
beforeAll(() => StoryMock.getLatestStories({}));
afterAll(() => jest.clearAllMocks());
test('calls find() for only published stories: { published: true, parent: null }', () => {
expect(StoryMock.find).toHaveBeenCalledWith({ published: true, parent: null });
});
test('calls sort() to sort in descending publishedAt order: { publishedAt: -1 }', () => {
expect(StoryMock.sort).toHaveBeenCalledWith({ publishedAt: -1 });
});
test(`calls limit() using default limit: ${defaultLimit}`, () => {
expect(StoryMock.limit).toHaveBeenCalledWith(defaultLimit);
});
test(`calls skip() using <default limit * default currentPage>: ${defaultLimit * defaultCurrentPage}`, () => {
expect(StoryMock.skip).toHaveBeenCalledWith(defaultLimit * defaultCurrentPage);
});
test('calls toResponseShape() on each Story instance found', () => {
expect(storyToResponseShapeSpy).toHaveBeenCalled();
});
test(`calls static addPagination() method with the first ${defaultLimit} stories result: { output: { stories }, limit: ${defaultLimit}, currentPage: ${defaultCurrentPage} }`, () => {
expect(StoryMock.addPagination).toHaveBeenCalledWith({
output: { stories: expectedStories },
limit: defaultLimit,
currentPage: defaultCurrentPage,
});
});
});
describe('with query pagination params', () => {
afterEach(() => jest.clearAllMocks());
test('executes the previously tested behavior using query param values: { limit: 5, currentPage: 2 }', async () => {
const limit = 5;
const currentPage = 2;
const storyToResponseShapeSpy = jest.spyOn(stories[0], 'toResponseShape');
const expectedStories = stories.slice(0, limit);
StoryMock.skip.mockImplementation(() => expectedStories);
await StoryMock.getLatestStories({ limit, currentPage });
expect(StoryMock.find).toHaveBeenCalledWith({ published: true, parent: null });
expect(StoryMock.sort).toHaveBeenCalledWith({ publishedAt: -1 });
expect(StoryMock.limit).toHaveBeenCalledWith(limit);
expect(StoryMock.skip).toHaveBeenCalledWith(limit * currentPage);
expect(storyToResponseShapeSpy).toHaveBeenCalled();
expect(StoryMock.addPagination).toHaveBeenCalledWith({
limit,
currentPage,
output: { stories: expectedStories },
});
});
test('limit value of 500 passed: enforces maximum value of 20 instead', async () => {
const limit = 500;
const maxLimit = 20;
const currentPage = 2;
StoryMock.skip.mockImplementation(() => stories.slice(0, maxLimit));
await StoryMock.getLatestStories({ limit, currentPage });
expect(StoryMock.limit).toHaveBeenCalledWith(maxLimit);
expect(StoryMock.addPagination).toHaveBeenCalledWith({
limit: maxLimit,
currentPage,
output: { stories: stories.slice(0, maxLimit) },
});
});
});
});
});
jest.spyOn(Post, "find").mockImplementationOnce(() => ({
sort: () => ({
limit: () => [{
id: '613712f7b7025984b080cea9',
text: 'Sample text'
}],
}),
}));
Here is how I did this with sinonjs for the call:
await MyMongooseSchema.find(q).skip(n).limit(m)
It might give you clues to do this with Jest:
sinon.stub(MyMongooseSchema, 'find').returns(
{
skip: (n) => {
return {
limit: (m) => {
return new Promise((
resolve, reject) => {
resolve(searchResults);
});
}
}
}
});
sinon.stub(MyMongooseSchema, 'count').resolves(searchResults.length);
This worked for me:
jest.mock("../../models", () => ({
Action: {
find: jest.fn(),
},
}));
Action.find.mockReturnValueOnce({
readConcern: jest.fn().mockResolvedValueOnce([
{ name: "Action Name" },
]),
});
All the above didn't work in my case, after some trial and error this worked for me:
const findSpy = jest.spyOn(tdataModel.find().sort({ _id: 1 }).skip(0).populate('fields'), 'limit')
NOTE: you need to mock the query, in my case I use NestJs:
I did the following:
find: jest.fn().mockImplementation(() => ({
sort: jest.fn().mockImplementation((...args) => ({
skip: jest.fn().mockImplementation((...arg) => ({
populate: jest.fn().mockImplementation((...arg) => ({
limit: jest.fn().mockImplementation((...arg) => telemetryDataStub),
})),
})),
})),
})),
findOne: jest.fn(),
updateOne: jest.fn(),
deleteOne: jest.fn(),
create: jest.fn(),
count: jest.fn().mockImplementation(() => AllTelemetryDataStub.length),
for me it worked like this:
AnyModel.find = jest.fn().mockImplementationOnce(() => ({
limit: jest.fn().mockImplementationOnce(() => ({
sort: jest.fn().mockResolvedValue(mock)
}))
}))

Jest test redux action with thunk doesn't cover statemets

Hello i have been trying to test a function with thunk and all the test passes but can't figure it out why the coverage doesn't not update or the test function does not cover the statement.
This is my function:
export const setFinished = (campaignId, userId, actionId, callback) => {
return async (dispatch, getState) => {
await axios.post(`http://bazuca.com:9000/campaigns/${campaignId}/progress`, {
userId,
actionId
}, { headers: { token: getState().app.token } })
.then((response) => {
})
.catch((error) => {
})
callback();
}
}
This is my last test (I have done like 3 different types and cant get the coverage to work)
describe("setFinished", () => {
it("works", () => {
const dispatch = jest.fn();
const callback = jest.fn(() => 'callback');
const getState = jest.fn();
let a = setFinished(1, 1, 1, callback)
expect(a).toHaveBeenCalledWith(1, 1, 1, callback);
a(dispatch, getState);
expect(callback).toHaveBeenCalled();
});
});
and i just get this in the coverage:
Maybe im doing it wrong? or should use another library?
There might be some things missing in your test setup. Especially the way you're making an assertion about the dispatch mock looks unusual. Without going into too much detail, just consider the following:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { setFinished } from 'path/to/your/actions';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('setFinished', () => {
it('works', () => {
// You have to make sure axios calls are mocked out properly
// at this point. I don't have a snippet handy for this so I
// left it out. But it would be similar to the following:
axios.mockImplementationOnce(() => ({
// Let the promise return whatever your response is for a
// positive test case
post: () => Promise.resolve({ isFinished: true })
}));
const expected = [
// I'm assuming something like this is dispatched in the
// .then handler of your action:
{ type: 'SET_FINISHED_SUCCESS' }
];
const store = mockStore({});
// Mock some arguments here
return store.dispatch(setFinished(1, 2, 3, () => null))
.then(() => expect(store.getActions()).toEqual(expected));
});
});
If axios is mocked out correctly, this will definitely achieve 100% coverage for this action if you also add a negative test case for the catch block.