Testing constructor method that invokes async method - reactjs

I've got a component that I'm testing that I instantiate in beforeEach with its dependencies:
let fixture: MyComponent
fixture = new MyComponent(dependency1, dependency2);
In the component class, the constructor calls a method that returns a Promise:
constructor( dep1, dep2){
this.initComponent() // returns a Promise
}
Naturally, the test setup test will fail:
test('it should get created', () => {
expect(fixture).toBeTruth()
}
With the expected error of:
Jest worker encountered 4 child process exceptions, exceeding retry limit
I tried to spy on the initComponent() method but that seems to fail because 1. it's private, and 2. I'm using TypeScript. Even if async/await I'm getting the error that await doesn't work in this type of expression, i.e.
await this.initComponent();
How else can I avoid this error?

Related

How to mock and check that a function from a library in Jest framework?

I have a button with "Download" text on it defined in ReactJS code. Now, I want to write a unit test to check that this function is getting called when this button is clicked. I wrote a unit test but it is not working.
import * as FileSaver from "file-saver"
it('File has to be saved when clicked on the "Download" button', () => {
jest.mock('file-saver', ()=>({saveAs: jest.fn()}));
fireEvent.click(component.getByText("Download"));
expect(FileSaver.saveAs).toBeCalled();
})
I'm getting this error:
Error: expect(received).toBeCalled()
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function anonymous]
pointing to expect(FileSaver.saveAs).toBeCalled(); line.
What's wrong?
So, as I stated in the comment, you have to move jest.mock('file-saver', ()=>({saveAs: jest.fn()})) from the body of the test to the top of the file, just under the imports. The reason for it is actually answered in the documentation here, but to wrap it up:
In your test file you are using import statement which fires up at start, before any code has a chance to run. Then you try to mock file-saver, but it is already imported with real implementation, not mocked. If you instruct jest to mock module at top of the file it will automatically hoist jest.mock calls to the top of the module so your function exportToExcel will receive mocked file-saver instead of the real one.
But if you really want to mock file-saver in the body of the test for some strange reason you would need to mock file-saver, then include all modules in test which use file-saver, something like this:
it('File has to be saved when clicked on the Export button', () => {
jest.mock('file-saver', ()=> ({ saveAs: jest.fn() }));
const component = require('/path/to/tested/componetn/on/which/click/event/is/fired');
const FileSaver = require('file-saver');
fireEvent.click(component.getByText("Download"));
expect(FileSaver.saveAs).toBeCalled();
})

How to test a method that uses async/await?

I've seen a lot of articles about how use async/await in your unit tests, but my need is the opposite.
How do you write a test for a method that uses async/await?
My spec is not able to reach any code after the 'await' line. Specifically, the spec fails in two ways.
1) HelloWorld.otherCall returns undefined instead of the return value I specify
2) HelloWorld.processResp never gets called
class HelloWorld {
async doSomething(reqObj) {
try {
const val = await this.otherCall(reqObj);
console.warn(val); // undefined
return this.processResp(val);
}
}
}
describe('HelloWorld test', function () {
let sut = new HelloWorld(); //gross simplification for demo purposes
describe('doSomething()', function () {
beforeEach(function mockInputs() {
this.resp = 'plz help - S.O.S.';
});
beforeEach(function createSpy() {
spyOn(sut, 'otherCall').and.returnValue( $q.resolve(this.resp) );
spyOn(sut, 'processResp');
});
it('should call otherCall() with proper arguments', function () {
//this test passes
});
it('should call processResp() with proper arguments', function () {
sut.doSomething({});
$rootScope.$apply(); //you need this to execute a promise chain..
expect(sut.processResp).toHaveBeenCalledWith(this.resp);
//Expected spy processResp to have been called with [ 'plz help SOS' ] but it was never called.
});
});
});
Running angular 1.5 and jasmine-core 2.6.
The .then of a promise is overloaded to handle either promises or values, and await is syntactic sugar for calling then.
So there is no reason your spy would be required to return a promise, or even a value. Returning at all, even if undefined, should trigger the await to fire, and kick off the rest of your async function.
I believe your problem is that you are not waiting for the doSomething promise to resolve before trying to test what it did. Something like this should get you more in the ballpark.
it('should call processResp() with proper arguments', async function () {
await sut.doSomething({});
// ...
});
Jasmine has Asynchronous Support. You can probably find a solution that way.
Personally, I think you should not test such methods at all.
Testing state means we're verifying that the code under test returns the right results.
Testing interactions means we're verifying that the code under test calls certain methods properly.
At most cases, testing state is better.
At your example,
async doSomething(reqObj) {
try {
const val = await this.otherCall(reqObj);
return this.processResp(val);
}
}
As long as otherCall & processResp are well covered by unit tests your good.
Do something should be covered by e2e tests.
you can read more about it at http://spectory.com/blog/Test%20Doubles%20For%20Dummies

Delay testing of a value until a promise has resolved

I'm attempting to test that a value is changed to true after a promise is resolved inside $onInit. I'm following, as best I can, the example in this Stack Overflow question/answer. Here is my code:
class TestCtrl {
constructor(SearchService) {
this.testValue = false;
this.SearchService = SearchService;
}
$onInit() {
this.SearchService.getResults()
.then(function () {
this.testValue = true;
});
}
}
TestCtrl.$inject = ['SearchService'];
And here's the test I'm attempting to run (using mocha, chai, sinon):
it('should work', function() {
ctrl = $componentController('test', {
SearchService: SearchService
}, {});
sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}});
ctrl.$onInit();
$rootScope.$apply();
ctrl.testValue.should.equal(true);
});
Should I be testing ctrl.testValue inside a then? Also, is using this example a bad idea because that example doesn't use a component with an $onInit lifecycle hook?
From what I've read, no, "don't use expect inside then in tests." But I'm not so sure based on what I've read elsewhere.
I wouldn't be surprised if I'm missing something obvious in how to test promises (maybe a stub wasn't the way to go?) and/or how to test what happens in the $onInit lifecycle hook.
If the question needs more details, please ask and I'll do my best to add them.
Edit: Checkout you $onInit method:
$onInit() {
this.SearchService.getResults()
.then(function () {
// `this` in anonymous function is reffering to window not the controller instance
this.testValue = true;
});
}
$onInit() {
var self = this;
self.SearchService.getResults()
.then(function () {
self.testValue = true;
});
}
Your example is correct
This is the way to test async code in angularjs - it is tested like synchronous code. Stubs' returning promises are resolved when you execute $rootScope.$apply().
Why it doesn't work
The promise returned from stub.resolves() is not an angular promise. It cannot be triggered to resolve using $rootScope, because it's not a part of angular's world. It's promise resolution queue is tied to something else and hence the need to test like you usually test async code.
Angular doesn't depend on JavaScript's native Promise implementation - it uses a light implementation of Q's promise library that is wrapped in a service called $q
The answer you have quoted uses the same service to create and return a promise from a stub
In order for your code to work - to test like you test synchronous code - you should return a $q promise (By wrapping a value in $q.when(value)) calling $rootScope.$apply() will execute the code in the then block, then proceed with the code below $rootScope.$apply() line.
Here is an example:
it('Sinon should work with angular promises', function () {
var resolved = 'resolved';
var promise = $q.when(resolved);
// Our async function
var stub = sinon.stub().returns(promise);
// Callback to be executed after the promise resolves
var handler = sinon.stub();
stub().then(handler); // async code
// The handler will be called only after $rootScope.$apply()
handler.callCount.should.equal(0);
// triggers digest which will resolve `ready` promises
// like those created with $q.when(), $q.resolve() or those created
// using the $q.defer() and deferred.resolve() was called
// this will execute the code inside the appropriate callback for
// `then/catch/finally` for all promises and then continue
// with the code bellow -> this is why the test is considered `synchronous`
$rootScope.$apply();
// Verify the handler was called and with the expected value
handler.callCount.should.equal(1);
handler.should.have.been.calledWith(resolved);
})
Here it is in action test promise synchronously in angular
For starters, you should read up on how Mocha expects you to test async code.
To start out with the quick bits:
You are on the right path - there are just some bits missing.
Yes you should do your test inside a then.
The example you linked to is fine. Just understand it.
There is absolutely no reason to avoid asserting a test inside a then. In fact, it is usually the only way to assert the resolved value of a promise.
The main problem with your test code is it tries to assert the result before it is available (as promises resolve in a later tick, they are asynchronous).
The main problem with the code you are trying to test is that there is no way of knowing when the init function has resolved.
We can deal with #2 by waiting for the stubbed SearchService.getResults to resolve (as we control the stub in the test), but that assumes too much knowledge of the implementation of onInit, so that is a bad hack.
Instead, we fix the code in TestCtrl, simply by returning the promise in onInit:
//main code / TestCtrl
$onInit() {
return this.SearchService.getResults()
.then(function () {
this.testValue = true;
});
}
Now we can simply wait for any call to onInit to resolve before we test what has happened during its execution!
To fix your test we first add a parameter to the wrapping test function. Mocha will see this and pass in a function that you can call when your test finishes.
it('should work', function(done) {
That makes it an async test. Now lets fix the test part:
ctrl.$onInit().then( () => {
ctrl.testValue.should.equal(true);
done(); // signals to mocha that the test is finished ok
}).catch(done); // pass any errors to the callback
You might find also find this answer enlightening (upvote if it helps you out). After reading it you might also understand why Mocha also supports dropping the done callback by returning a promise from the test instead. Makes for shorter tests:
return ctrl.$onInit().then( () => {
ctrl.testValue.should.equal(true);
});
sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}}); is not returning a promise. Use $q.
I would suggest doing this:
ctrl = $componentController('test', {
SearchService: SearchService
}, {});
let deferred =$q.defer();
deferred.resolve({response:{data: 'data'}});
sinon.stub(SearchService, 'getResults').resolves(deferred.promise);
ctrl.$onInit();
$rootScope.$apply();
ctrl.testValue.should.equal(true);
You don't need to test ctrl.testValue inside a then. And generally, I would recommend not assert inside .then() in your specs. The specs will not fail if the promise never gets resolved. That can give you a false sense of security when in reality, your tests are not doing anything. But that's just my opinion.
Your test will pass once the stub returns a promise. Ideally, I would recommend using $httpBackend if the service is making an http call.
Cheers.

Angular 2 Testing Issue

Whilst attempting to run integration tests with Angular 2 and Karma test runner the following issue became clear. A test was always passing even when it should have been failing.
The issue occurs when the expect() method is placed inside the subscribe() method of an Observable.
The need to do this arose as the test would subscribe to the Observable and then continue processing the rest of the test before the Observable has finished executing.
However, placing the expect within the subscribe() method automatically causes the test to pass even when there are very obvious syntax errors:
it('should pass or fail', inject([Service], (_service : Service) => {
let result = _service.returnObservable();
result.subscribe((sfasdasdaa23231gr) => {
expect(r.isAfhahzdzd vailable).not.35q6w623tyrg /.0824568sfn toBe(truDDIDIDIDDIe);
});
}));
the previous code passes, but how? there are syntax errors everywhere. Does anyone know where this issue lies? In the testing or in the subscribe() method?
Because it's asynchronous processing, you should add the async method:
it('should pass or fail', async(inject([Service], (_service : Service) => {
let result = _service.returnObservable();
result.subscribe((sfasdasdaa23231gr) => {
expect(r.isAfhahzdzd vailable).not.35q6w623tyrg /.0824568sfn toBe(truDDIDIDIDDIe);
});
})));

Chai-spies: AssertionError: expected { Spy }

I am using chai-spies to make sure a function in my controller is invoked, here's my test:
it('Should show right season and analysts when competition has been selected', function (done) {
scope.selectedCompetition = scope.competitions[3];
var spy = chai.spy(scope.selectedCompetitionChanged);
scope.selectedCompetitionChanged();
expect(spy).to.have.been.called();
done();
});
where scope.selectedCompetitionChanged is a function. The test fails with the following error:
AssertionError: expected { Spy } to have been called
at Context.<anonymous> (base/tests/client/controllers/prediction.js?02f216981852d0775780926989e7266c6afb0af6:61:30)
How come this happen if I invoke explicitly call the function?
Thanks
Just for the record, I think you understood wrong the docs. With this:
var spy = chai.spy(scope.selectedCompetitionChanged);
You are just wrapping the function scope.selectedCompetitionChanged inside another function spy, so if you want to see the number of calls you have to use the new spy() instead of the original scope.selectedCompetitionChanged().
Another way to track an object method is as follows:
var spy = chai.spy.on(scope, 'selectedCompetitionChanged');
Now you can call scope.selectedCompetitionChanged() as usual and it will count as a spy call.

Resources