I suspect Jasmine halts after running an async mocked function 2nd time in the same function, but I cant seem to find the reason or the correct workaround.
The getDialogAnswer is a jquery dialog wrapped in a promise and async await function. This is an implementaiton of "delete" + "are you sure" dialog boxes. Everything works as expected in the running application.
the 2 calls
let optiontext = 'Delete project from plan?'
let deleteProject = await getDialogAnswer(title, optiontext, choices, defaultvalue)
...
optiontext = 'REALLY delete project from plan???<br>There is no going back'
deleteProject = await getDialogAnswer(title, optiontext, choices, defaultvalue)
The mock function
const getDialogAnswer = jasmine.createSpy('Mock_getDialogAnswer').and.returnValues('yes','yes');
The test function is created with async
it("should delete on yes + yes", async () => {
This passes
expect(getDialogAnswer).toHaveBeenCalledTimes(2)
but following 4 of this type fails - says called 0 times.
expect(mainPart.clearSelected).toHaveBeenCalledTimes(1)
The last I check is
expect(unsaved_changes).toBe(true);
which reports "Expected null to be true." But a console output writes correct true, so I know the function finished correctly.
The test is running in Jasmine HTML standalon Specrunner. I have tested with both 4.6,4.6 and 5.0Beta.
If I change the second call
deleteProject = await getDialogAnswer(title, optiontext, choices, defaultvalue)
to
deleteProject = 'yes'
getDialogAnswer is of course only run once, but the rest of the expectaions pass!
And if I just remove the await of the second call to be like this
deleteProject = getDialogAnswer(title, optiontext, choices, defaultvalue)
It all passes test - but then I cannot delete in the real application! Here the delete is not carried through.
I have gone through all I could find on async and spyes in the doc https://jasmine.github.io/index.html, and tried the method
const getDialogAnswer = jasmine.createSpy('Mock_getDialogAnswer').and.returnValues(
Promise.resolve('yes'),Promise.resolve('yes'));
With same result
I have searched here, but mainly found how to setup test functions. I have a suspecion that I may have setup my mock fucntion incorrect or that Jasmine maybe has a flaw here. But I cant find any documentaion or other thread that shed some light on this. This might be a lead, but it seems to me that I am already doing it right. How to test async function with spyOn? Or am I missing something tiny but essential?
Solved the issue myself.
The culprit was "missing" async/await keywords in application code. Not needed for function but for test only!
By posting the question I apparently set some thoughts in motion! It sometimes help solving an issue by stating and explaining it to others!
This is the boiled down and scrambled original code, which works in the application, but not in test:
I have had to insert the async block in the original object method, to make the 2 async/promise dialog calls work (And yes, messy and ripe for refactoring!)
function main_obj() {
this.objmethod1 = function () {
const async_objmethod = async function(){
let deleteProject = await getDialogAnswer()
deleteProject = await getDialogAnswer()
this.objmethod2();
this.objmethod3()
this.objmethod4();
unsaved_changes = true;
method5(this.name);
}
async_objmethod.bind(this)();
};
this.objmethod2 = function () {};
this.objmethod3 = function () {};
this.objmethod4 = function () {};
}
By adding async + await the test runs and passes as intended and expected
this.objmethod1 = async function () {
...
await async_objmethod.bind(this)();
};
In the application, there is no need to wait for the function to finish, but to test it properly it was needed.
I have included working specrunner html, code + testcode for completenes
specrunner.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="jasmine_lib/jasmine-5.0.0-beta.0/jasmine_favicon.png">
<link rel="stylesheet" href="jasmine_lib/jasmine-5.0.0-beta.0/jasmine.css">
<script src="jasmine_lib/jasmine-5.0.0-beta.0/jasmine.js"></script>
<script src="jasmine_lib/jasmine-5.0.0-beta.0/jasmine-html.js"></script>
<script src="jasmine_lib/jasmine-5.0.0-beta.0/boot0.js"></script>
<script src="jasmine_lib/jasmine-5.0.0-beta.0/boot1.js"></script>
<script src="maincode2.js" > </script>
<script src="test_spec.js"></script>
</head>
<body></body>
</html>
maincode.js
function main_obj() {
this.objmethod1 = async function () {
const async_objmethod = async function(){
let deleteProject = await getDialogAnswer()
deleteProject = await getDialogAnswer()
this.objmethod2();
this.objmethod3()
this.objmethod4();
unsaved_changes = true;
method5(this.name);
}
await async_objmethod.bind(this)();
};
this.objmethod2 = function () {};
this.objmethod3 = function () {};
this.objmethod4 = function () {};
}
test_spec.js
const getDialogAnswer = jasmine.createSpy('Mock_getDialogAnswer').and.returnValue(
Promise.resolve('yes'),
Promise.resolve('yes'),
);
const method5 = jasmine.createSpy('Mock_method5')
let unsaved_changes;
// var for main_inst instance set in 'before-funcs'
let main_inst
beforeEach(function() {
main_inst = new main_obj();
unsaved_changes = null
spyOn(main_inst, 'objmethod2')
spyOn(main_inst, 'objmethod3')
spyOn(main_inst, 'objmethod4')
});
describe("Test makemain_inst.js", () => {
describe("Test new jquery ui dialog functions", () => {
describe("test dialog uses", () => {
describe("test this.objmethod1()", () => {
describe("test yes no", () => {
it("should delete on yes + yes", async () => {
// ------------ setup -------------
// -------- Call ----------------
const x = await main_inst.objmethod1()
// ------------- verify ------------
expect(getDialogAnswer).toHaveBeenCalledTimes(2)
expect(main_inst.objmethod2).toHaveBeenCalledTimes(1)
expect(main_inst.objmethod3).toHaveBeenCalledTimes(1)
expect(main_inst.objmethod4).toHaveBeenCalledTimes(1)
expect(method5).toHaveBeenCalledTimes(1)
expect(unsaved_changes).toBe(true);
});
});
});
});
});
});