Jasmine test case for PrimeNg Confirmation Service not working - javascript

I have a function which executes some operations in the "accept" call of PrimeNg Confirmation service. I tried to write a Unit test case for it as following:
fit('submit preview config', fakeAsync(() => {
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
const confirmationService = TestBed.get(ConfirmationService);
tick(200);
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
});
flush();
}));
The problem is the execution never goes inside callFake. The test case passes but the operation never takes place. Any ideas are welcome.
This is the function I want to test:
submitPreviewForm() {
const messageContent = `<strong>You have updated the following fields:</strong><br/>
<span class="subText" style="font-size: 12px; color: blue;">•${Array.from(this.updatedHighlightedFields).join('<br/> •')}</span>
<br/>
<strong>This will clear JIRA data from DB. Are you sure you want to proceed?</strong>`;
this.confirmationService.confirm({
message: messageContent,
accept: () => {
...
}
});
}
I am using V6 of PrimeNg.
I saw the implementation on this Stack Overflow question:
Angular Unit Test of a PRIME ng confirmation service

Your order of operations seems to be a bit off, you need to spy before calling submitPreviewform.
Try this:
fit('submit preview config', fakeAsync(() => {
const confirmationService = TestBed.get(ConfirmationService); // grab a handle of confirmationService
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
}); // spy on confirmationService.confirm now
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
tick(200);
flush();
}));

Related

Jest custom testing API, how to correct code frame

I'm looking to simplify my project's testing API where I am aiming for something like this:
testThing((t) => {
t(33);
t(44);
t(42);
})
Now I don't know how to get Jest to show the correct code frames for failed expect's. This is my current stab at an implementation:
const testThing = (callback: any) => {
callback((n: any) => {
test(n.toString(), () => {
expect(n).toBe(42);
});
});
};
Which results in the testThing definition to be shown for every failed test case. Here's a replit if you want to see it in action: https://replit.com/#grgr/jest-frame-issue#thing.test.js

Matcher error: received value must be a mock or spy function

I'm writing tests (with Jest and React Testing Library) for a form React component. I have a method that runs on form submit:
const onSubmit = (data) => {
// ...
setIsPopupActive(true);
// ...
};
and useEffect that runs after isPopupActive change, so also on submit:
useEffect(() => {
if (isPopupActive) {
setTimeout(() => {
setIsPopupActive(false);
}, 3000);
}
}, [isPopupActive]);
In the test, I want to check, whether the popup disappears after 3 seconds. So here's my test:
it('Closes popup after 3 seconds', async () => {
const nameInput = screen.getByPlaceholderText('Imię');
const emailInput = screen.getByPlaceholderText('Email');
const messageInput = screen.getByPlaceholderText('Wiadomość');
const submitButton = screen.getByText('Wyślij');
jest.useFakeTimers();
fireEvent.change(nameInput, { target: { value: 'Test name' } });
fireEvent.change(emailInput, { target: { value: 'test#test.com' } });
fireEvent.change(messageInput, { target: { value: 'Test message' } });
fireEvent.click(submitButton);
const popup = await waitFor(() =>
screen.getByText(/Wiadomość została wysłana/)
);
await waitFor(() => {
expect(popup).not.toBeInTheDocument(); // this passes
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 3000);
});
});
However, I'm getting the error:
expect(received).toHaveBeenCalledTimes(expected)
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function setTimeout]
What am I doing wrong?
Jest 27 has breaking changes for fakeTimers. It seems Jest contributors doesn't update documentation on time. This comment on Github issues confirms it. Moreover, here related PR.
Well, you can solve your problem by two ways.
Configure Jest to use legacy fake timers. In jest.config.js you can add line (but it not works for me):
module.exports = {
// many of lines omited
timers: 'legacy'
};
Configure legacy fake timers for individually test suite, or even test:
jest.useFakeTimers('legacy');
describe('My awesome logic', () => {
// blah blah blah
});
It's preferably to use new syntax based on #sinonjs/fake-timers. But I can't find working example for Jest, so I'll update this answer as soon as possible.
The below approach worked
beforeEach(() => {
jest.spyOn(global, 'setTimeout');
});
afterEach(() => {
global.setTimeout.mockRestore();
});
it('Test if SetTimeout is been called', {
global.setTimeout.mockImplementation((callback) => callback());
expect(global.setTimeout).toBeCalledWith(expect.any(Function), 7500);
})
In your case setTimeout is not a mock or spy, rather, it's a real function. To make it a spy, use const timeoutSpy = jest.spyOn(window, 'setTimeout'). And use timeoutSpy in the assertion.
You could also test not the fact of calling the setTimeout function, but assert that setIsPopupActive was called once, and with false. For this you might need to do jest.runOnlyPendingTimers() or jest.runAllTimers()

Jest: Mock a _HOC_ or _curried_ function

Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};
The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.

Angular - Unit testing Subject()?

I have a Angular service that simply uses a Subject, but I am unsure of how to write unit tests for it.
I saw [this thread][1], but I didn't find it terribly helpful.
I have attempted to mock next() but I am more than a little lost.
You should spy on service.serviceMsg and not service, because next() method appears to be on serviceMsg subject.
it('should catch what is emitted', () => {
const nextSpy = spyOn(service.serviceMsg, 'next');
service.confirm(ACTION);
expect(nextSpy).toHaveBeenCalled();
});
EDIT :
You should also change the way you are creating service instance. What you show in your code is applicable only for component instance creation
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MessageService]
});
service = TestBed.get(MessageService); // get service instance
httpMock = TestBed.get(HttpTestingController);
});
Firstly you can just subscribe to your Subject and inside expect some value and after that just execute method which will emit that:
it('should catch what is emitted', () => {
service.serviceMsg.subscribe(msg => {
expect(msg).toEqual(something);
});
service.confirm(value); // this should match what you expect above
});

Smooch: How to do postback dependent state transition?

I am trying to transition the script from one state to another based on Smooch postback payloads; but getting error code H12.
Consider the example https://github.com/smooch/smooch-bot-example
Say I modify the script https://github.com/smooch/smooch-bot-example/blob/master/script.js as follows
start: {
receive: (bot) => {
return bot.say('Hi! I\'m Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye) );
}
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
The intention is that the's bot's state would transition depending on the postback payload.
Question is, how do I make that happen?
My approach was add
stateMachine.setState(postback.action.payload)
to the handlePostback method of github.com/smooch/smooch-bot-example/blob/master/heroku/index.js
However, that threw an error code H12. I also experimented with
stateMachine.transition(postback.action,postback.action.payload)
to no avail.
I got the same issue with the [object Object] instead of a string. This is because the state you get or set with a function is contained in an object, not a string... I fixed it with this code inside index.js, replacing the existing handlePostback function in the smooch-bot-example GitHub repo:
function handlePostback(req, res) {
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const postback = req.body.postbacks[0];
if (!postback || !postback.action) {
res.end();
};
const smoochPayload = postback.action.payload;
// Change conversation state according to postback clicked
switch (smoochPayload) {
case "POSTBACK-PAYLOAD":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
default:
stateMachine.bot.say("POSTBACK ISN'T RECOGNIZED") // for testing purposes
.then(() => res.end());
};
}
Then inside script.js all you need to do is define states corresponding to the exact postback payloads. If you have multiple postbacks that should take the user to other states, just add them to the case list like so :
case "POSTBACK-PAYLOAD-1":
case "POSTBACK-PAYLOAD-2":
case "POSTBACK-PAYLOAD-3":
case "POSTBACK-PAYLOAD-4":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
Note that you should not write break; at the end of each case if the outcome you want is the same (here : setting the state and prompting the corresponding message).
If you want to handle other postbacks differently, you can add cases after the break; statement and do other stuff instead.
Hope this helps!
Postbacks won't automatically transition your conversation from one state to the next, you have to write that logic yourself. Luckily the smooch-bot-example you're using already has a postback handler defined here:
https://github.com/smooch/smooch-bot-example/blob/30d2fc6/heroku/index.js#L115
So whatever transition logic you want should go in there. You can do this by creating a stateMachine and calling receiveMessage() on it the same way handleMessages() already works. For example:
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
stateMachine.receiveMessage({
text: 'whatever your script expects'
})
Alternatively, you could have your handlePostback implementation call stateMachine.setState(state) and stateMachine.prompt(state) independently, if you wanted to have your postbacks behave differently from regular text responses.
If you want to advance the conversation based on a postback you'll have to first output the buttons from the bot's prompt (so you can handle the button click in the receive), modify the handlePostback function in index.js, then handle the user's "reply" in your receive method - try this - modify script.js like so:
start: {
prompt: (bot) => bot.say(`Hi! I'm Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye)`),
receive: (bot, message) => {
switch(message.text) {
case 'Yes':
return bot.say(`Ok, great!`)
.then(() => 'hi')
break;
case 'No':
return bot.say(`Ok, no prob!`)
.then(() => 'bye')
break;
default:
return bot.say(`hmm...`)
.then(() => 'processing')
break;
}
}
},
hi: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
Then modify the handlePostback function in index.js so that it treats a postback like a regular message:
function handlePostback(req, res) {
const postback = req.body.postbacks[0];
if (!postback || !postback.action)
res.end();
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const msg = postback;
// if you want the payload instead just do msg.action.paylod
msg.text = msg.action.text;
stateMachine.receiveMessage(msg)
.then(() => res.end())
.catch((err) => {
console.error('SmoochBot error:', err);
res.end();
});
}
Now when a user clicks your button it will be pushed to the stateMachine and handled like a reply.

Categories