Test state value after promise resolved - unit-testing

Given an component that make a login ...
onSubmit(e) {
e.preventDefault();
const { userStore, data } = this.props;
this.setState({isLogin: true});
const promise = userStore.login(data);
promise.then(() => {
this.setState({isLogin: false});
})
}
How can I create a test that validate if isLogin state is false after the promise is resolved?
So far, I have ..
it('resolved submit should set isLogin to false', () => {
mockApi.onPost('/authenticate').reply(200);
const userStore = new UserStore();
let fromPromiseLogin;
userStore.login = jest.fn()
.mockImplementationOnce((data) => {
fromPromiseLogin = userStore.login(data);
return fromPromiseLogin;
});
const wrapper = mount(
<FormLogin userStore={userStore} />,
);
wrapper.find('form').simulate('submit');
// HOW CAN I WAIT/OR CALLBACK FOR THE PROMISE ? I TRIED >
whenWithTimeout(
() => fromPromiseLogin && fromPromiseLogin.state !== PENDING,
() => expect(wrapper.state().isLogin).toBeFalsy(),
);
});
But a exeption
[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[When#21] TypeError: Cannot read property 'state' of undefined

EDIT:
This answer is no longer relevant after the question has been edited.
Have you tried to test the result of the promise with .resolves function as described here?
expect(myPromise).resolves.toEqual('SomeValue');
Or using async/await:
const result = await myPromise;
expect(result).toEqual('SomeValue');

Related

How to mock a function in jasmine

I am trying to unit test the following code using jasmine but I can't seem to mock the checkStatus function. What am I seem to get wrong? It seems like when i actually call the Booking.saveBooking method it is not using the mocked version of the checkStatus. Please help.
Booking.js
const checkStatus = (id) => {
.. // some code here
return new Promise((resolve, reject)=>{
resolve(value);
});
}
const saveBooking =(req) => {
checkStatus(req.id).then(()=>{
//save booking here ..
}).catch((error)=>{
throw new Error();
});
}
module.exports ={saveBooking, checkStatus}
booking.Spec.js
const Booking = require('Booking');
describe('Booking',()=>{
const req = {
id: 2133,
customer_name: 'John Smith',
contact_number: '888-8888',
contact_email: 'cam888#example.com'
};
it('Should check the status', async ()=>{
spyOn(Booking, "checkStatus").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
});
await Booking.saveBooking(req);
expect(Booking.checkStatus).toHaveBeenCalled()
});
});
I am getting an error Error: Expected spy checkStatus to have been called.
It seems like saveBooking method it is not using the mocked version of the checkStatus.

Jest - Mock promisified SQS calls

Let's assume that I have a QueueClass with a method send, that gets some data as parameter
which then sends to a SQS queue.
I want to write 2 tests:
One that tests that the MessageBody and QueueUrl keys have the expected values passed in.
One that in case of error an exception gets thrown.
The method to be tested looks like this:
My send method:
async send(data) {
return SQS.sendMessage({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
})
.promise()
.catch((error) => {
// Throw exception ...
});
}
The test I have for that method:
const aws = require('aws-sdk');
jest.mock('aws-sdk', () => {
const SQSMocked = {
sendMessage: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
SQS: jest.fn(() => SQSMocked),
};
});
sqs = new aws.SQS();
test('my test', async () => {
const data = {};
await QueueClass.send(data);
expect(sqs.sendMessage).toHaveBeenCalledWith({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
});
});
That test gives me the following error:
TypeError: Cannot read property 'catch' of undefined
I did try adding catch: jest.fn() to the SQSMocked object, the exact same way I do with promise, but kept getting the same error.
The thing is that when I change the method that I am trying to test so it uses try-catch block instead of .promise() and .catch() :
async send(data) {
try {
return SQS.sendMessage({
MessageBody: JSON.stringify(data),
QueueUrl: 'queue_url_here',
});
} catch (error) {
// Throw exception ...
}
}
my test passes, so that makes me think that this is not necessarily an issue about properly mocking the SQS.
Any ideas why when using .promise() and .catch() my test fails ?
Also how could I test a case where an Error gets thrown by the queue ?
I would like to be able to do something like this:
await expect(sqs.sendMessage)
.resolves
.toEqual(...);
OR
await expect(sqs.sendMessage)
.rejects
.toThrowError(new Error('Some error thrown.'));
promise is stubbed and returns undefined, this is the reason why it doesn't return a promise that could be chained. It's supposed to return a promise, as the name suggests.
Since values may be different in different tests, it's better to expose it as a variable. sendMessage can be exposed as well for assertions:
const mockPromiseFn = jest.fn();
const mockSendMessage = jest.fn().mockReturnThis();
jest.mock('aws-sdk', () => {
return {
SQS: jest.fn().mockReturnValue({
sendMessage: mockSendMessage,
promise: mockPromiseFn
})
};
});
It doesn't make sense to test it with await expect(sqs.sendMessage).rejects... because it tests the code you've just written.
It likely should be:
mockPromiseFn.mockRejectedValue(new Error(...));
await expect(QueueClass.send(data)).rejects.toThrowError(...);
expect(mockSendMessage).toBeCalledWith(...);
This is potentially a mistake:
async send(data) {
try {
return SQS.sendMessage(...);
} catch (error) {
// Throw exception ...
}
}
try..catch is unable to catch asynchronous errors from async return, also sendMessage return value wasn't converted to a promise.
It should be:
async send(data) {
try {
return await SQS.sendMessage(...).promise();
} catch (error) {
// Throw exception ...
}
}
duplicate questions
How to mock AWS sqs call for unit testing
just add the method around to be called like so...
class EventService {
static async sendFifoMessage(
url,
message,
groupId,
dedupeId,
) {
const sqsMessageRequest = {
QueueUrl: url,
MessageBody: JSON.stringify(message),
MessageGroupId: groupId,
};
if (!!dedupeId) {
sqsMessageRequest.MessageDeduplicationId = dedupeId;
}
return await new SQS().sendMessage(sqsMessageRequest).promise();
}
}
import AWS = require('aws-sdk');
const URL = 'URL';
const MESSAGE = 'MESSAGE';
const GROUP_ID = 'GROUP_ID';
const DEDUPE_ID = 'DEDUPE_ID';
const BAD_REQUEST = 'BAD_REQUEST';
jest.mock('aws-sdk', () => {
const SQSMocked = {
sendMessage: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return {
SQS: jest.fn(() => SQSMocked),
};
});
const sqs = new AWS.SQS({
region: 'us-east-1',
});
describe('EventService', () => {
beforeEach(() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockReset();
});
afterAll(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.resetAllMocks();
});
describe('sendFifoMessage', () => {
const messageResult = {
QueueUrl: URL,
MessageBody: JSON.stringify(MESSAGE),
MessageGroupId: GROUP_ID,
MessageDeduplicationId: DEDUPE_ID,
};
it('sendMessage successfully', async() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockResolvedValueOnce('mocked data');
await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID);
expect.assertions(2);
expect(sqs.sendMessage).toBeCalledWith(messageResult);
expect(sqs.sendMessage().promise).toBeCalledTimes(1);
});
it('sendMessage throws', async() => {
(sqs.sendMessage().promise as jest.MockedFunction < any > ).mockRejectedValueOnce(BAD_REQUEST);
expect(async() => await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID)).rejects.toThrowError(
new Error(BAD_REQUEST),
);
expect(sqs.sendMessage).toBeCalledWith(messageResult);
expect(sqs.sendMessage().promise).toBeCalledTimes(1);
});
});
});

`data` is undefined in apollo useMutation Promise

Why is data undefined in Promise even though it appears in html below?
I dont want to use .then((data) =>
const LoginPage: React.SFC<{}> = () => {
const [login, { loading, data, error }] = useMutation<Q, V>(MUTATION)
const onSubmit = event => {
event.preventDefault()
login({
variables: {
email,
password
}
}).then(() => {
console.log(data) // <-- undefined
}).catch(() => {
console.log(error) // <-- OK
})
}
return <div>
<form onSubmit={onSubmit}> ... </form>
{data && <pre>{JSON.stringify(data)}</pre>} // <-- OK
</div>
}
A variation of the original question. Not sure if it works as intended though because the result of the mutation's Promise is undefined. Thanks.
const [login] = useMutation(MUTATION);
const onSubmit = event => {
event.preventDefault()
login({
variables: {
email,
password
}
}).then((result) => {
console.log(result) // <-- undefined
}).catch(() => {
console.log(error) // <-- OK
})
}
This is working as intended. The state update caused by calling the state updater function returned by a useState hook is asynchronous. useState is used under the hood by useMutation to manage the various bit of state returned by the hook (data, loading, error, etc.) so this state will also update asynchronously. This means the Promise returned by login can resolve, and in doing so, the appropriate state will be updated, just not immediately. Once it is updated, the component is rerendered and you see the updated value rendered in your component.
If you need to utilize the data returned by the mutation in some way as soon as the Promise resolves, then you need to do so by extracting it from the resolved value of the Promise instead of relying on the component state.

Sending e.target as a DOM node in jest/enzyme

//test.js
const map = {};
document.addEventListener = jest.fn((event, cb) => {
map[event] = cb;
});
const wrapper = mount(<AccordianDropdown />);
it('Should not close dropdown on document click inside', () => {
map.click({
target: <div role="button"/>,
});
});
This is my index.js file:-
// index.js
componentDidMount() {
document.addEventListener('click', this.closeOnDocumentClick);
}
componentWillUnmount() {
document.removeEventListener('click', this.closeOnDocumentClick);
}
closeOnDocumentClick = (e) => {
// If event happened in the dropdown, ignore it
if (!(this.ref && isFunction(this.ref.contains) && this.ref.contains(e.target)) && this.state.open) {
this.setState({ open: false });
}
};
On running test getting this error TypeError: Failed to execute 'contains' on 'Node': parameter 1 is not of type 'Node'.. Please help me understand how to test this piece of code.
Ok Found a solution for this,
In the wrapper object call the getDOMNode() method to get the DOM node(s) element(s).
If you pass this element as the event.target value. You will not get an exception.
If not you could create an element using JSDOM
const dom = new JSDOM();
const newDiv = dom.window.document.createElement("div");
and then
test('...', () => {
const mockedEvent = { target: wrapper.getDOMNode()[0] };
wrapper
.find('ClickAwayListener')
.props()
.onClickAway(mockedEvent);
});
or If you just want to avoid the exception, TypeError: Failed to execute 'contains' on 'Node': parameter 1 is not of type 'Node'. pass null as the target to the simulated event or event callback handler.
test('...', () => {
const mockedEvent = { target: null };
wrapper
.find('ClickAwayListener')
.props()
.onClickAway(mockedEvent);
});
Hope this code will help you.

Unit testing bluebird promise bind function

I have the following function that uses bind to bind a context to the then chains. When i try and test it, it throws
TypeError: redisClient.hgetallAsync(...).bind is not a function
myFunc() {
let self = this;
return redisClient.hgetallAsync('abcde')
.bind({ api: self })
.then(doStuff)
.catch(err => {
// error
});
}
Test
let redisClient = { hgetallAsync: sinon.stub() };
describe('myFunc', () => {
beforeEach(() => {
redisCLient.hgetallAsync.resolves('content!');
});
it('should do stuff', () => {
return myFunc()
.should.eventually.be.rejectedWith('Internal Server Error')
.and.be.an.instanceOf(Error)
.and.have.property('statusCode', 500);
});
});
The hgetallAsync stub is returning a plain JS Promise rather than a Bluebird promise.
To use a Bluebird promise, you need to tell Sinon to do so using .usingPromise().
let redisClient = { hgetallAsync: sinon.stub().usingPromise(bluebird.Promise) };
Documentation Link:
http://sinonjs.org/releases/v4.1.2/stubs/#stubusingpromisepromiselibrary