One of the most common reasons for failing test cases in Jasmine with AngularJS is asynchronous behavior. This happens when test assertions execute before the asynchronous operation (such as an HTTP request or promise resolution) is completed.
For example, you might see errors like:
"Expected undefined to be 100."
"Unresolved promise error"
"Async operation did not complete"
This guide will explain why async tests fail, how to handle asynchronous code in Jasmine, and best practices for writing stable async test cases.
1. Why Do Async Tests Fail?
1.1. Test Runs Before Async Operation Completes
Jasmine runs test cases synchronously, so if an async operation (like $http
or $q
) hasn’t resolved yet, the test assertion executes too early, leading to failure.
Example of a Failing Test:
it('should fetch data from the API', function () {
var result;
myService.getData().then(function (data) {
result = data;
});
expect(result.value).toBe(100); // Fails because result is still undefined
});
Problem: result
is undefined
when the test assertion runs because getData()
is asynchronous.
1.2. Missing $httpBackend.flush()
If your service makes an HTTP call, Jasmine needs to flush the request manually.
Without flushing:
it('should fetch data from API', function () {
var result;
myService.getData().then(function (response) {
result = response.data;
});
expect(result).toEqual({ value: 100 }); // Fails because HTTP request is pending
});
✅ Fixed by flushing the request:
it('should fetch data from API', function () {
$httpBackend.whenGET('/api/data').respond(200, { value: 100 });
var result;
myService.getData().then(function (response) {
result = response.data;
});
$httpBackend.flush(); // Triggers the HTTP response
expect(result).toEqual({ value: 100 }); // Now it works
});
2. How to Fix Async Test Failures in Jasmine
2.1. Use done()
for Promises
Jasmine provides a done
callback for handling asynchronous operations.
Correct Way to Handle Promises:
it('should fetch data from API using done()', function (done) {
$httpBackend.whenGET('/api/data').respond(200, { value: 100 });
myService.getData().then(function (response) {
expect(response.data.value).toBe(100);
done(); // Signals Jasmine that async operation is complete
});
$httpBackend.flush(); // Required for HTTP requests
});
Why this works:
done()
ensures that the test waits for the async operation to complete.- The test case only finishes when
done()
is called.
2.2. Use $rootScope.$apply()
for $q
Promises
When a service returns a promise using $q
, you must manually trigger the AngularJS digest cycle.
🚀 Correct Test for $q
Promises:
it('should resolve the promise', function () {
var result;
myService.getData().then(function (data) {
result = data;
});
$rootScope.$apply(); // Forces promise resolution
expect(result.value).toBe(100);
});
Why this works:
$rootScope.$apply()
triggers the digest cycle, ensuring the promise resolves before assertions run.
2.3. Use async/await
with done()
For better readability, you can use async/await
in Jasmine tests.
Using async/await
:
it('should resolve promise with async/await', async function (done) {
$httpBackend.whenGET('/api/data').respond(200, { value: 100 });
const response = await myService.getData();
expect(response.data.value).toBe(100);
done(); // Ensures async operation is complete
$httpBackend.flush();
});
Why this works:
await
pauses execution untilgetData()
resolves.done()
ensures Jasmine doesn’t finish the test too early.
3. Debugging Tips for Async Test Failures
3.1. Log the Data Before the Assertion
If a test fails, check if the async function is returning data:
it('should fetch data from API', function (done) {
myService.getData().then(function (response) {
console.log(response); // Check the response before assertion
expect(response.data.value).toBe(100);
done();
});
});
3.2. Use spyOn
to Mock Async Behavior
If a service isn’t working as expected, spy on it and return a resolved promise.
beforeEach(inject(function (_myService_, _$q_) {
myService = _myService_;
spyOn(myService, 'getData').and.returnValue($q.resolve({ value: 100 }));
}));
it('should resolve data from spy', function () {
var result;
myService.getData().then(function (data) {
result = data;
});
$rootScope.$apply(); // Resolves the spy's promise
expect(result.value).toBe(100);
});
4. Summary of Fixes & Best Practices
Problem | Solution |
---|---|
Test runs before async operation completes | Use done() or $rootScope.$apply() |
Missing $httpBackend.flush() | Call $httpBackend.flush(); |
$q promise not resolving | Use $rootScope.$apply(); |
async/await not working | Always call done(); after await |
Undefined response in test | Add console.log(response); before assertions |
Mock service returns unexpected data | Use spyOn(myService, 'getData').and.returnValue($q.resolve(...)) |