how do I test a class in mocha? - mocha.js

In my journey towards TDD, I am using Mocha, chai and sinon.
There certainly is a learning curve there.
My goal is to write a test to verify that method4 was executed. How do I achieve that ?
//MyData.js
class MyData {
constructor(input) {
this._runMethod4 = input; //true or false
this.underProcessing = this.init();
}
method1() { return this.method2() }
method2() {
if (this._runMethod4) {
return this.method4();
} else {
return this.method3();
}
method4(){
return thirdPartyAPI.getData();
}
method3(){
return someAPI.fetchData();
}
init(){
return this.method1();
}
}
MyData.spec.js
describe('MyData', () => {
it('should execute method 4', function() {
let foo = new MyData(true);
foo.underProcessing.then(()=>{
// How do I verify that method4 was executed ??
expect(foo.method4.callCount).to.equal(1);
});
});
})

Here's an example:
const expect = require('chai').expect;
const sinon = require('sinon');
const sinonTest = require('sinon-test');
sinon.test = sinonTest.configureTest(sinon);
sinon.testCase = sinonTest.configureTestCase(sinon);
describe("MyData", () => {
it("should execute method 4", sinon.test(function() {
let spy = this.spy(MyData.prototype, 'method4');
let foo = new MyData(true);
return foo.underProcessing.then(() => {
expect(spy.callCount).to.equal(1);
});
}));
});
As an additional requirement, I added sinon-test because it's really useful to help clean up spies/stubs after the test has run.
The main feature is this line:
let spy = this.spy(MyData.prototype, 'method4');
This replaces MyData.prototype.method4 by a Sinon spy, which is a pass-through function (so it calls the original) that will record how exactly it was called, how often, with what arguments, etc. You need to do this before the instance is created, because otherwise you're too late (the method might already have been called through the chain of method calls that starts with this.init() in the constructor).
If you want to use a stub instead, which is not pass-through (so it won't call the original method), you can do that as well:
let spy = this.stub(MyData.prototype, 'method4').returns(Promise.resolve('yay'));
So instead of calling thirdPartyAPI.getData() and returning its result, method4 will now return a promise resolved with the value yay.
The remaining code should speak for itself, with one caveat: an explicit return in front of foo.underProcessing.then(...).
I assume that foo.underProcessing is a promise, so it's asynchronous, which means that your test should be asynchronous as well. Since Mocha supports promises, when you return a promise from a test, Mocha will know how to deal with it properly (there's an alternative method of making an asynchronous test with Mocha, involving callback functions, but when you're testing promise-based code you shouldn't really use those since it's easy to run into timeouts or swallowed exceptions).

Related

Cypress returning Synchronous value within Async command?

So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.
Here is my function:
searchCustomer(searchText: string) {
this.customerInput.type(searchText)
this.searchButton.click()
cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length > 0) {
{Click some drop downdowns }
return data < ----I think here is the problem
} else {
{Do other stuff }
}
})
}
and in my test case itself:
let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)
So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)
Cypress basically told me I was wrong via the error message:
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then
returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue
cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another
cy.then().
So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?
Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.
Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.
searchCustomer(searchText: string): Chainable<any[]> {
this.customerInput.type(searchText)
this.searchButton.click()
return cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length) {
{Click some drop downdowns }
return data
} else {
{Do other stuff }
return []
}
})
}
// using
searchCustomer('my-customer').then((data: any[]) => {
if (data.length) {
}
})
Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.
It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.
you just need to add return before the cy.wait
here's a bare-bones example
it("test", () => {
function searchCustomer() {
return cy.wait(100).then(intercept => {
const data = {text: "my data"}
return data
})
}
const myCustomer = searchCustomer()
myCustomer.should("have.key", "text")
myCustomer.its("text").should("eq", "my data")
});

Why allowRespy is set to false by default in jasmine?

I am a beginner in Jasmine and I was wondering, why the default value for allowRespy is set to false?
I found this answer, where explained that jasmine.spyOn() always returns the first created spy, so I assume that the reason for flag it to prevent usage of mock with state.
I can come out with a simplified example where such a behavior could be problematic:
describe("TestedService", () => {
const testedService = TestBed.inject(TestedService) // configuration is ommited for simplicity
it("calls foo only once", () => {
const fooSpy = spyOn(testedService, 'foo')
testedService.doAction()
expect(fooSpy).toHaveBeenCalledOnce()
fooSpy = spyOn(testedService, 'foo') // creating new spy to check if it will be called for the second time
testedService.doAction()
expect(fooSpy).not.toHaveBeenCalledOnce() // fails the test because it still points to the same spy,that was called few moments later.
})
})
Another, more realistic example of problematic behavior is when you want to reset a spy used in your test by creating a new spy with spyOn function, but instead of it you will not be created and spy with old state will be used.
Example
describe("TestedService", () => {
beforeEach(() => {
TestBed.inject(TestedService) // configuration is ommited for simplicity
spyOn(TestedService, 'foo').and.returnValue(100)
})
....
it('tests TestedService behavior when foo returns undefined', () => {
spyOn(TestedService, 'foo') // expect it to set spy return value to undefined,
but it don't, so the test below does not tests behavior when foo returns undefined
.....
})
})
Other examples and corrections would be appreciated!

JasmineJS + AngularJS: How to mock the delay inside Spy#callFake()

Let's assume I have a service function that returns me the current location. And the function has callbacks to return the location. We can easily mock the function like as follows. But I wanted to introduce some delay (let's say 1 sec) before the callFake() invokes the successHandler(location).
Is there a way to achieve that?
xxxSpec.js
spyOn(LocationService, 'getLocation').and.callFake(function(successHandler, errorHandler) {
//TODO: introduce some delay here
const location = {...};
successHandler(location);
}
LocationService.js
function getLocation(successCallback, errorCallback) {
let location = {...};
successCallback(location);
}
Introducing delay in Javascript is easily done with the setTimeout API, details here. You haven't specified if you are using a framework such as Angular, so your code may differ slightly from what I have below.
It does not appear that you are using Observables or Promises for easier handling of asynchronous code. Jasmine 2 does have the 'done' callback that can be useful for this. Something like this could work:
it( "my test", function(done) {
let successHandler = jasmine.createSpy();
spyOn(LocationService, 'getLocation').and.callFake(function(successHandler, errorHandler) {
setTimeout(function() {
const location = {...};
successHandler(location);
}, 1000); // wait for 1 second
})
// Now invoke the function under test
functionUnderTest(/* location data */);
// To test we have to wait until it's completed before expecting...
setTimeout(function(){
// check what you want to check in the test ...
expect(successHandler).toHaveBeenCalled();
// Let Jasmine know the test is done.
done();
}, 1500); // wait for longer than one second to test results
});
However, it is not clear to me why adding the timeouts would be valuable to your testing. :)
I hope this helps.

jasmine test mocking a function by calling spyOn

I am pretty new in Jasmine test.
Lett say I have this sample.js file:
(function() {
function myFunction() {
do some thing and then make a call to MyOtherFunciton
}
myOtherFunciton(p1, p2, p3) {
//do some stuff in here
}
module.exports = {
myOtherFunciton,
myFunction
}
})();
Now I have this jasmine test does the following
const mySampleFile = require(./sample.js);
spyOn(mySampleFile, "myOtherFunciton").and.callFack(()=>{
});
mySampleFile.myFunction();
expect(mySampleFile.myOtherFunciton).toHaveBeenCalled();
The problem I am experiencing is that it makes a call to real myOtherFunciton function but not the mocked one. Why is that ?
It's a function scope problem you are running into. The function myOtherFunciton() called from within myFunction() is not the same as mySampleFile.myOtherFunciton(), as you found out. To fix will require a slight refactor of your original code (don't you love it when testing exposes such things?). I added some console.logs below just to be clear where the execution context is going during testing.
Suggested refactor:
(function() {
exports.myFunction = function() {
// do some thing and then make a call to MyOtherFunciton
console.log('inside myFunction');
exports.myOtherFunciton('a', 'b', 'c'); // scoped to exports, now can be mocked
// myOtherFunciton('a', 'b', 'c'); // <-- don't call it like this: scoped within IIFE
}
exports.myOtherFunciton = function(p1, p2, p3) {
console.log('inside original myOtherFunciton');
//do some stuff in here
}
// module.exports = {
// myOtherFunciton,
// myFunction
// }
})();
Here is a working StackBlitz showing the test now passing. Click on 'console' below the Jasmine test window to see the output.
I hope this helps.

Mocha single test with promises fails and passes at the same time

I am currently writing some tests in a legacy system and am confused about a test result here.
I have one test here which fails as expected but mocha shows as a result 1 passing and 1 failing.
I am using Bluebird Promises, mocha, chai-as-promised and sinon with sinon-chai for spies and stubs. This is the test (I have removed stuff not needed for understanding my problem):
describe("with a triggerable activity (selection)", function () {
beforeEach(function stubData() {
stubbedTriggerFunction = sinon.stub(trigger, "newParticipation");
activityLibStub = ... // stub
selectionLibStub = ... // stub
fakeActivities = ... // simple data with just ONE element
fakeSelection = ... // simple data with just ONE element
// stub methods to return synthetic data
activityLibStub.returns(new Promise(function (resolve) {
resolve(fakeActivities);
}));
selectionLibStub.withArgs(1).returns(new Promise(function (resolve) {
resolve(fakeSelection);
}));
});
afterEach(function releaseStubs() {
// restore stubs...
});
it("should call the newParticipation function", function () {
var member = memberBuilder.buildCorrect();
trigger.allActivities(member)
.then(function () {
return expect(stubbedTriggerFunction).to.not.have.been.called;
})
.done();
})
});
This test fails as expected, because the function is actually invoked. However, mocha tells me that one test passed and one test failed. This is the only test I have implemented in this module.
I am pretty sure this has something to do with the promises but I don't seem to figure out what it is. Also if I add
.catch(function(){
chai.assert.fail();
})
after the then-block, mocha still claims that one test passed. The method is also just invoked one time and I have only one synthetic dataset which I am working with. So I don't see what it is which tells mocha that this has passed while failed...
Any ideas?
Regards, Vegaaaa
Return your promise, return your promise, return your promise. Let's chant it all together now "Return, yoooooooour promise!"
it("should call the newParticipation function", function () {
var member = memberBuilder.buildCorrect();
return trigger.allActivities(member)
.then(function () {
return expect(stubbedTriggerFunction).to.not.have.been.called;
});
})
});
I've also removed .done() because that's not generally useful with Bluebird and would be downright harmful here. Mocha still needs to use your promise.
(It's use is generally discouraged, see here.) If you do not return your promise, then Mocha treats you test as synchronous and most likely that's going to be successful because your test is not really testing anything synchronously. Then if you get an asychronous failure, Mocha has to decide what failed exactly and will do its best to record the failure but it can lead to funny things like having an incorrect number of tests or the same test being reported as failed and successful!

Resources