Angular 1.5 && Async/Await && jasmine tests - javascript

I already looked everywhere but could not find a solution yet for my particular case.
We are using angular 1.5 and a Karma/Jasmine setup for unit tests. In the initial source code, I used ES2017 async/await in the controller. That seemed to work fine as long as I added $apply of $digest manually at the end.
So for example:
async function loadData() {
try {
vm.isLoading = true;
vm.data = await DataService.getData();
$scope.$apply();
}
catch (ex) {
vm.isLoading = false;
}
}
To write an automated test for this particular function, I tried to mock DataService.getData with Jasmine's spyOn. So, I did something like this:
spyOn(DataService, 'getData').and.returnValue($q.when(fakeResult));
Adding the spy worked, but when running a test, the code seems to get struck and not resolve with fakeResult. I tried adding $digest/$apply in the tests itself but could not fix it. I also did a lot of research, but still have no clue.
Does somebody have a clue?
Edit: testing the same method with $q promises works fine, but I would really like to use async/await...

I don't know if your setup is similar, but in our environment I had to do a couple of things to get transpiled async/await statements to resolve in jasmine tests.
In our tests, I was trying to mock out services that would return promises. I found that returning $q.when didn't work. Instead, I had to return actual A+ standard Promises. My guess is that Angular's $q promises aren't fully complaint with the standard and wouldn't work as a substitute.
Note that since we use PhantomJS for our tests, I had to add polyfills to get those Promises.
Many times in my tests I would have to wrap some of the expect statements in a setTimeout block. Again, my assumption is that the promises need an additional "tick" to process.

So the reason why there is no result is because nothing after await is executed. I had a similar issue when I was testing async/await inside react components.
I found somewhere that a testing method for this is as follows:
You have to use something similar to https://www.npmjs.com/package/jasmine-async-suite - this is what I used. It is for asynchronous tests where you are expecting a promise.
There could be also an other problem, that after the promise is resolved, the test stops, because there is nothing to wait for :). It could be very tricky.
So you have to manually call the method, in your case DataService.getData(), and use the .then() method, where you can put your expect statements - because of this step, your test is waiting for resolving the promise.
Here is an example of my code (I am also using async functions in tests):
it.async('should call `mySpecialMethod`', async () => {
const arrayToResolve = [ { data: 'data' } ];
const SomeService = context.SomeService;
spyOn(props, 'mySpecialMethod');
spyOn(SomeService, 'myMethodReturningPromise');
.and.returnValue(Promise.resolve(arrayToResolve));
//the method `myMethodReturningPromise` is called in componentDidMount
const wrapper = mount(<MyReactComponent {...props} />, context);
expect(SomeService.myMethodReturningPromise).toHaveBeenCalled();
//now I am calling the method again
await SomeService.myMethodReturningPromise();
//`mySpecialMethod` is calling after the `await` in my code
expect(props.mySpecialMethod).toHaveBeenCalled();
//after that I am changing the state
expect(wrapper.state().arrayToResolve).toEqual(arrayToResolve);
});
I hope this helps you :)

You can use the library async-await-jasmine:
import * as angular from 'angular';
import 'angular-mocks';
import {yourModule} from "./your-module-path";
import {LoadDataService} from './load-data';
import {$it} from "async-await-jasmine";
import {IRootScopeService} from "angular";
describe('Calculator', () => {
let $httpBackend: angular.IHttpBackendService;
beforeEach(() => {
angular.mock.module(calculatorModule.name);
angular.mock.inject((_$httpBackend_) => {
$httpBackend = _$httpBackend_;
});
});
$it('should loadData', async () => {
$httpBackend.when('GET', '/loadData').respond('{"value": 5}');
let sum = loadData(1, 4);
$httpBackend.flush();
expect(await sum).toBe(10);
});
});

Related

Is it wrong to use await within jest's expect?

I read the official jest documentation on async/await as well as numerous blog posts dealing with the topic. All are describing different ways of awaiting a value on which assertions are made as well as waiting for the test function to complete. None of them included the following pattern, however I don't see why it should cause trouble: Assume I have a
const getValue: () => Promise<string>
and that I'm testing it with
test("await in expect", async () => {
expect(await getValue()).toBe("b")
})
I can't reproduce the issue with an implementation like
const getValue = () => new Promise((resolve) => {
setTimeout(() => {
resolve("b")
}, 4000)
})
however I'm experiencing about 20% fails of an integration test where getValue runs queries against a real database because afterAll function is called early and terminates the connection.
I'm aware that I can overcome this with resolves and other use of await or done or else, e.g.
test("await in expect", async () => {
await expect(getValue()).resolves.toBe("b")
})
and I already managed to overcome the issue in my real world integration test with this approach.
However, I'd like to broaden my understanding of what's going on and what I'm doing when using await inside expect().
I'm using Jest 27.
There's a good reason why one should create real reproducers for issues and questions: The issue might turn out to be something completely different.
In this case the TypeORM Repository.save promise apparently returns before the saved instance is flushed into the database or cache or whatever which is queried by Repository.find.
I might investigate further or just go with a 100ms sleep after save. And yes, you can quote that in yet another blog post about why ORMs are troublesome.

How do I properly test using a mocked service that returns a Promise?

I'm new to Angular and I'm still trying to figure out how things work for it. I'm currently having trouble testing a Component that depends on a Service that returns a Promise. The function I'm testing is structured like the following:
success: boolean;
borrowBook() {
this.bookService.borrow(this.selectedBook.id)
.then(() => {
this.success = true;
})
.catch((error: BorrowingError) => {
this.success = false;
});
}
Now, I'm not really sure if something like the code above is considered idiomatic, but that's how I wrote the code. In my unit test, I mocked the bookService using jasmine.createSpyObj function, and defined the stub as follows:
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
However, my test fails saying that component.success is undefined when I expected it to be truthy. My test is programmed as follows:
it('test', async(() => {
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
//setup pre-conditions here...
component.borrowBook(();
expect(component.success).toBeTruthy();
}));
My impression is that the expectations are being checked even before the Promise is handled accordingly.
I chanced upon this article about testing asynchronous code in Angular, which stated that the flushMicrotasks function can be used to run all the async components before checking expectations. This is only provided through the fake zone created through the fakeAsync, so in my code I used that instead of just async.
it('test', fakeAsync(() => { //used fakeAsync instead
mockBookService.borrow.and.returnValue(Promise.resolve(testResult));
//setup pre-conditions here...
component.borrowBook(();
flushMicrotasks(); //added this before checking expectations
expect(component.success).toBeTruthy();
}));

Method inside class is not getting executed in unit test

I have a class which contains methods.Now after initializing the class i want to invoke the methods but my test flow is not to the method and getting error like Uncaught error outside test suite.Below is my code
describe('Test Stu positive', () => {
it("Test Stu positive", (done) => {
const stuDetails = new masterDetails(getdetails);
expect(stuDetails.retrieveStudent(res => {
console.log(res);
done()
}))
});
});
Now, in the above code i am not able to print console.log(res);. What i am doing wrong?
I believe that you are using Mocha as your testing framework and looks like the error is not being handled by mocha, because is an asynchronous operation and you are not passing the error to the done callback method as described in the mocha documentation
it is really hard to tell how your function works, if it returns a promise or if it just uses a callback and the error is handled inside the function, so I can't provide you a code example on how to accomplish this. if you mind providing your function declaration I can update my answer with an example solution.

Return promises from Ember 2.16 Component Integration Tests Mocked action

I am creating tests for my Ember 2.16 application, and running into an issue where the code is dependent on a promise being returned from an external action.
const promise = this.sendAction('action')
promise.then(() => {
//do stuff
});
A majority of my code is based in these .then and .catch conditionals on the promise, so I want to be able to return a promise that was successful and that failed. I have heard of Sinon, but unfortunately it is only for Ember 3.4 and above.
test('', function(assert){
this.set('action', () => {
// do assertions
return new Promise(() => {return true} );
});
});
Inside my integration test I am able to mock out the action, but I run into the "promise" being undefined. I have attempted to return Text, or other values, but when putting a debugger into the component the promise is always undefined.
I can get around this by adding a conditional that checks to see if there is a promise, but since the majority of my code is inside these .then and .catchconditionals I want my tests to step through these to increase the code coverage.
How would I return a promise from a mocked out action in an Integration Test?
You can stub your call to sendAction like so in your integration test:
test('', function(assert) {
this.sendAction = () => RSVP.resolve(true);
this.render(hbs`{{some-component sendAction=sendAction}}`);
});
This will return a promise as your code expects.
I recommend using the RSVP library in cases where you need to use promises.
It turns out I was returning the promise incorrectly.
return new Promise((reject) => {
reject(true);
});
Was able to return the promise as "rejected".

Accomplish Promise Chaining

Background
I have a nodejs server running and I installed the promise package which follows the promise api specs.
Since I succeeded in making denodeify(fn, length) work, I am now in the process of chaining promises, but I am failing to grasp the main concepts.
What I tried
By reading the documentation example on the specification page, I reached the following code:
let Promise = require("promise");
let write = Promise.denodeify(jsonfile.writeFile);
let read = Promise.denodeify(jsonfile.readFile);
read("dataFile.txt").then( () => {
write("./testFile.txt", "hello_World", TABS_FORMATTING).then(console.log("file complete"));
});
Which is quite different from the examples I see, for example, in the Solutions Optimist tutorial:
loadDeparture( user )
.then( loadFlight )
.then( loadForecast );
Objective
My objective is to make my code as beautiful as the example I showed before, but I don't understand how I can make as concise as it is right now.
Question
1 - What changes do I need to perform in my code to achieve that level?
The given example uses named function to make it look as good as it can get, but that can be a bit redundant because then you're creating functions for every little thing in the chain. You must pick and choose when to use named functions over anonymous functions.
One thing you must also realize is that to chain promises you must return them.
So to make it a proper chain you must return the write method so it is passed down to the next step.
Also make sure that the catch() method is used at the bottom of every promise chain so that errors aren't silently swallowed.
Note that in the example here I'm using the ES2015 arrow functions to return the write() method as that makes it looks better(which seemed to be the purpose of your question).
let Promise = require("promise");
let write = Promise.denodeify(jsonfile.writeFile);
let read = Promise.denodeify(jsonfile.readFile);
read("dataFile.txt")
.then(() => write("./testFile.txt", "hello_World", TABS_FORMATTING))
.then(results => console.log("file complete", results))
.catch(error => console.error(err));
I'd recommend reading this article for some best practices.
Nesting promises kind of defeats the purpose because it creates pyramid code (just like callbacks).
The main concept that may be escaping you is that you can return inside a then and the returned value (can be a promise or a value) can then be accessed in a chained then:
read("dataFile.txt").then( () => {
return write("./testFile.txt", "hello_World", TABS_FORMATTING);
}).then( () => {
console.log("file complete");
});
Now, you can extract the functions:
function writeTheFile() {
return write("./testFile.txt", "hello_World", TABS_FORMATTING);
}
function consoleLog() {
console.log("file complete");
}
read("dataFile.txt")
.then(writeTheFile)
.then(consoleLog);

Categories

Resources