sinon spy is not called in a stub with async function - javascript

Using sinon and enzyme I wanna test the following component:
// Apple.js
class Apple extends Component {
componentDidMount = () => {
this.props.start();
Api.get()
.then(data => {
console.log(data); // THIS IS ALWAYS CALLED
this.props.end();
});
}
render () {
return (<div></div>);
}
}
If I just check endApy.called, it's always false. But wrapping it in a setTimeout will make it pass. Why console.log() is always called but not the props.end? Why setTimeout fixes it? Is there a better way of doing this?
// Apple.test.js
import sinon from 'sinon';
import { mount } from 'enzyme';
import Api from './Api';
import Apple from './Apple';
test('should call "end" if Api.get is successfull', t => {
t.plan(2);
sinon
.stub(Api, 'get')
.returns(Promise.resolve());
const startSpy = sinon.spy();
const endApy = sinon.spy();
mount(<Apple start={ startSpy } end={ endApy } />);
t.equal(startSpy.called, true); // ALWAYS PASSES
t.equal(endSpy.called, true); // ALWAYS FAILS
setTimeout(() => t.equal(endApy.called, true)); // ALWAYS PASSES
Api.get.restore();
});

Api.get is async function and it returns a promise, so to emulate async call in test you need to call resolves function not returns:
Causes the stub to return a Promise which resolves to the provided value. When constructing the Promise, sinon uses the Promise.resolve method. You are responsible for providing a polyfill in environments which do not provide Promise.
sinon
.stub(Api, 'get')
.resolves('ok');

your console.log(data) always happens because your Promise does resolve, it just does so after the test has finished, which is why the assertion fails.
By wrapping it in a setTimeout you create another event in the loop, which allows your Promise to resolve before the test finishes, meaning that your assertion will now pass.
This is a fairly common problem when unit testing asynchronous code. Often resolved in wrapping the assertions in setImmediate and calling done from the callback of setImmediate.
https://stackoverflow.com/a/43855794/6024903

I ran into a similar problem with a spy and using await Promise.all(spy.returnValues)worked fine for me. This resolves all the promises and afterwards I can check spy.called.

Related

Promise not rejecting correctly in Jest test

I am trying to test a console.error output when a promise rejects using Jest. I am finding that the promise seems to be resolving after my test has run, causing the test to fail.
Example Function:
export default function doSomething({ getData }) {
const success = data => {
//do stuff with the data
}
const handleError = () => {
//handle the error
}
getData.then(response => success(response)).catch(error => {
console.error(error)
handleError()
})
}
Example Test File:
import doSomething from "doSomething"
it("should log console.error if the promise is rejected", async () => {
const getData = new Promise((resolve, reject) => {
reject("fail");
});
global.console.error = jest.fn();
await doSomething({ getData });
expect(global.console.error).toHaveBeenCalledWith("fail");
})
//fails with global.console.error has not been called
When I was exploring the problem, I noticed that if I add in a console.log and await that, it works.
This will pass...
import doSomething from "doSomething"
it("should log console.error if the promise is rejected", async () => {
const getData = new Promise((resolve, reject) => {
reject("fail");
});
global.console.error = jest.fn();
await doSomething({ getData });
await console.log("anything here");
expect(global.console.error).toHaveBeenCalledWith("fail");
})
How do I test for this correctly? Should I refactor how my getData function is being called? It needs to get called as soon as the doSomething function is called.
Why is the original test failing?
The trick for understanding why the first test example won't pass is in digging into what the await operator is actually doing. From the Mozilla docs:
[rv] = await expression;
expression - A Promise or any value to wait for.
rv - Returns the fulfilled value of the promise, or the value itself if it's not a Promise.
In your first test the value of expression is the return value of the doSomething function. You aren't returning anything from this function, so the return value will be undefined. This isn't a Promise, so nothing for await to do, it will just return undefined and move on. The expect statement will then fail, as you haven't actually awaited the inner promise: getData.then(...).catch(...).
To fix the test, without adding in the extra line, await console.log("anything here");, simply return the inner promise from the doSomething function, so that the await operator will actually operate on the Promise.
export default function doSomething({ getData }) {
return getData.then(...).catch(...);
...
}
Is this the right way to test this?
I don't think that there is anything majorly wrong with how the doSomething function is written. This kind of dependency injecting usually makes functions easier to test than trying to mock out the inner workings of a function.
I would only recognise though that because you are injecting a Promise (getData), and resolving it within the function, you have made the doSomething function asynchronous (which is what made it more complicated to test).
Had you resolved the Promise and then called doSomething on the value it resolves to, getData.then(doSomething).catch(handleError), your doSomething function would have been synchronous and easier to test. I would also say that writing it this way makes it much more verbose that something asynchronous is going on, whereas the original, doSomething({ getData }), hides that within the doSomething function body.
So nothing strictly incorrect, but maybe a few things to think about that might make testing easier and code more verbose. I hope that helps!

Verify a method call inside of a rxjs subscription

I have an angular app which I'm trying to create a unit test for. There is a particular method which creates an observable through a complex chain of observable pipes and promises, and then subsequently subscribes to that created observable. I need to verify a method call is made inside of the subscription.
The structure is something like this:
private obs$: Observable<T>;
private subscription: Subscription;
methodToTest(): void {
this.obs$ = from( ... ).then( /* chain of piped observables */ );
this.subscription = this.obs$
.subscribe(
(value: T) => {
// bunch of logic
methodToBeVerifiedWasCalled();
},
(error) => { /* error handling */ }
);
}
My test so far looks like this:
it('the method to be verified is successfully called', () => {
// various mocking set up
this.mockApi.methodThatReturnsAPromise.and.returnvalue(Promise.resolve());
// etc.
this.classUnderTest.methodToTest();
// assertions for the various mocks...
expect(this.mockApi.methodThatReturnsAPromise).toHaveBeenCalled();
// etc.
// assert that the target method was called:
expect(this.classUnderTest.methodToBeVerifiedWasCalled).toHaveBeenCalled();
expect(this.classUnderTest.subscription).toBeDefined();
});
Of course, this test fails at the last assertions because the methodToBeVerifiedWasCalled is actually called inside of the subscribe block and the test is just running through it synchronously. (Here, classUnderTest is a spy object with methodToBeVerifiedWasCalled being spied on.)
Since the method assigns a value to the observable, I can't just subscribe to classUndertest.obs$ in an async test and verify the value returned (which doesn't really help solve the problem anyway.) How can I verify that the method inside of the subscribe block is called?
I cannot change the source code, just the tests.
You need to make your test asynchronous and then await something after methodToTest(). If methodToTest() does not return a promise or observable that you could await then you will be forced to just await until some time has passed. Something like:
function delay(ms) {
return new Promise(resolve => setTimeout(ms, resolve));
}
it('the method to be verified is successfully called', async (done) => {
// various mocking set up
this.mockApi.methodThatReturnsAPromise.and.returnvalue(Promise.resolve());
// etc.
const result = this.classUnderTest.methodToTest();
// if the method actually returned a promise you could: await result;
// since it does not, you can just await time:
await delay(1000); // wait 1 second
// assertions for the various mocks...
expect(this.mockApi.methodThatReturnsAPromise).toHaveBeenCalled();
// etc.
// assert that the target method was called:
expect(this.classUnderTest.methodToBeVerifiedWasCalled).toHaveBeenCalled();
expect(this.classUnderTest.subscription).toBeDefined();
// tell jasmine test is done
done();
});

Error: Timeout of 30000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves

I am trying to write tests for electron using spectron.
This is my code.
describe ('Application launch', function(done) {
this.timeout(30000);
const app = new Application({
path: electronBinary,
args: [baseDir],
});
before(() => app.start());
after(() => app.stop());
it('shows an initial window', async () => {
await app.client.waitUntilWindowLoaded();
const count = await app.client.getwindowcount();
assert.equal(count,1);
});
});
However, When I run npm test The error I get is
1) Application launch "before all" hook:
Error: Timeout of 30000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
2) Application launch "after all" hook:
Error: Application not running
at Application.stop (node_modules\spectron\lib\application.js:58:48)
at Context.after (test\spec.js:19:19)
Do I need to add any functions to the existing hooks?
It appears to be happening in your before all method. Notice that your error says 1) Application launch "before all" hook:.
So your actual test function looks fine to me. And I don't see a beforeAll anywhere in this example code so I would say there is one of two possible problems.
There is a beforeAll method with an issue somewhere not in this code sample.
The before hook shown here is returning a non-promise object.
In your code you are using a lambda function to do the work of before but if app.start() is returning an object which is not a promise then that would be your issue. Try refactoring it more like this:
before(() => {
app.start()
})
If your app.start() function is asynchronous you may need to pass the done handler into it:
before((done) => {
app.start(done)
})
Or possibly convert your app.start() function to return a promise which should resolve it. You may need to add async () => await app.start() but I don't think that would be necessary for a single expression like this.
You didn't use "done" as a callback in your it-function. You also don't have to use done in the describe callback. Also, since done() already makes your code asynchronous, you don't have to use the async keyword.
My solution:
describe ('Application launch', function() {
this.timeout(30000);
const app = new Application({
path: electronBinary,
args: [baseDir],
});
before(() => app.start());
after(() => app.stop());
it('shows an initial window', (done) => {
await app.client.waitUntilWindowLoaded();
const count = app.client.getwindowcount();
assert.equal(count,1);
done();
});
});
Hope it helps!

How to test if function A was called inside the test function B if it was called asynchronously

So basically I have the function I want to test which we will call function A. I want to test if function B was called inside function A. The problem is that function B is being called inside function A asynchronously through a resolved Promise. This will cause sinon assertion to fail because the test will finish before function B is called!
Here is a working code scenario.
const sinon = require('sinon');
describe('functionToBeTested', () => {
it('someFunction is called', () => {
// spy on the function we need to check if called
const spy = sinon.spy(someClass.prototype, 'someFunction');
// call the function being tested
functionToBeTested();
// check if spy was called
sinon.assert.called(spy);
});
});
class someClass {
someFunction() {
console.log('Hello');
}
}
const somePromise = Promise.resolve();
function functionToBeTested() {
const instance = new someClass();
// some synchronous code here
// if instance.someFunction() is called here the test will pass
// .
// .
// .
somePromise.then(() => {
instance.someFunction();
// the function is called and Hello is printed but the test will fail
})
// .
// .
// .
// some synchronous code here
// if instance.someFunction() is called here the test will pass
}
Your example is a bit unconventional. You have functionToBeTested which has a dual behavior (sync and async at the same time). When you put this method under test, the behavior should be well known and standardized before hand, so that you can structure the test and assertions accordingly.
The problem in this scenario is that you try to validate the behavior of the function is sync mode, although the inner parts work in a fire-and-forget fashion - i.e. there is no dependency on the result of the instance.someFunction() method.
If functionToBeTested() returned a promise - thus being async by design, that would be straightforward for your test scenario. But in this case, you will need an unconventional testing approach as well. That means that if you do something like:
describe('functionToBeTested', () => {
it('someFunction is called', (done) => {
// spy on the function we need to check if called
const spy = sinon.spy(SomeClass.prototype, 'someFunction');
// call the function being tested
functionToBeTested();
setTimeout(() => {
// check if spy was called
sinon.assert.called(spy);
done();
}, 10);
});
});
the test would pass. What happened here is that we declared the test async by making use of the done argument in the callback. Also, we added a timer to mimic a delay prior to checking whether the spy was called.
Since the 'fire-and-forget' call only prints out a message, waiting for 10ms would be enough. If the promise took longer to complete, the wait time should be adjusted.
As stated before, unconventional implementations require unconventional approaches. I would suggest that you reconsider your requirements and redesign the solution.

How do I fail a Node unit test on the catch of a Promise?

I'm doing some unit tests using Node.js and I want to fail a test like this:
doSomething()
.then(...)
.catch(ex => {
// I want to make sure the test fails here
});
I'm using Assert, so I found Assert.Fails. The problem is that fails expects actual and expected, which I don't have. The Node documentation doesn't say anything about them being required, but the Chai documentation, which is Node compliant, say they are.
How should I fail a test on the catch of a promise?
You can use a dedicated spies library, like Sinon, or you can implement a simple spy yourself.
function Spy(f) {
const self = function() {
self.called = true;
};
self.called = false;
return self;
}
The spy is just a wrapper function which records data about how the function has been called.
const catchHandler = ex => ...;
const catchSpy = Spy(catchHandler);
doSomething()
.then(...)
.catch(catchSpy)
.finally(() => {
assert.ok(catchSpy.called === false);
});
The basic principle is to spy on the catch callback, then use the finally clause of your promise to make sure that the spy hasn't been called.
If you will use mocha, then elegant way would be as following:
describe('Test', () => {
it('first', (done) => {
doSomething()
.then(...)
.catch(done)
})
})
If your Promise fail, done method will be invoked with the thrown exception as a parameter, so code above is equivalent to
catch(ex => done(ex))
In mocha invoking done() with the parameter fails the test.
Have you considered Assert.Ok(false, message)? It's more terse.
Assert.fail is looking to do a comparison and display additional info.

Categories

Resources