AngularJS httpBackend asynchronous response
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In AngularJS tests, $httpBackend is not a real server that eventually answers on its own. It is a deterministic mock backend from ngMock, so the test controls when the response is released by calling $httpBackend.flush().
Understand the $httpBackend Model
The async part of $httpBackend testing is often misunderstood. Your application code still behaves as if it were waiting for HTTP, but in the test there is no real network delay unless you simulate it indirectly through the order in which you flush pending requests.
That means the normal testing flow is:
- define the expected request and response
- trigger the application code that makes the request
- call
$httpBackend.flush()
Without the flush step, the mocked response stays pending and the promise chain does not complete.
The Basic Testing Pattern
Here is a small AngularJS service:
And the matching test:
The critical step is flush(). That is what releases the mocked response into the promise chain.
Why It Is Still Asynchronous in Structure
Even though the timing is deterministic, your code is still structured around asynchronous promises. That matters because the test is not bypassing the application's async behavior. It is just controlling exactly when the transport mock resolves.
This is the value of $httpBackend: your tests stay fast and predictable without relying on real network timing.
That predictability is also why $httpBackend was so widely used in AngularJS unit tests. It gave developers control over request expectations and resolution order without requiring a real HTTP server.
Multiple Requests and Partial Flushes
If your code makes several requests, you can flush them together or control how many pending responses are released.
In more complex tests, flush(count) is useful when you want to release only part of the pending queue at a time. That lets you test intermediate application state between responses.
This is often the closest thing to simulating "asynchronous responses arriving later" in an AngularJS unit test. The responses are still controlled by the test, but you can release them in stages.
Promise Chains After the Response
Any .then(...) logic attached to the $http promise runs after the backend is flushed.
So $httpBackend.flush() advances the transport mock, and the promise callbacks follow from there.
Digest Cycle Questions Can Still Appear
In many cases, flushing the backend is enough because AngularJS processes the $http promise resolution as part of that flow. But if your test also depends on other watcher-driven updates, you may still need digest-cycle handling in addition to the HTTP flush.
It helps to keep the two ideas separate:
- flushing mocked HTTP work
- propagating unrelated scope or watcher updates
If the test result depends on both, handle both deliberately instead of assuming the backend flush covers all state propagation automatically.
Keep the Verification Hooks
These cleanup checks are not optional decoration:
They catch tests that forgot to flush, expected the wrong URL, or left unresolved requests hanging around. Without them, a test suite can quietly pass while still hiding broken HTTP-test setup.
AngularJS Is Legacy Software Now
AngularJS is end-of-life, but many teams still maintain older codebases. In those projects, $httpBackend remains a valuable tool because it matches the original AngularJS testing model exactly.
If you are working in a newer Angular ecosystem, the tooling and testing patterns are different. This article is specifically about AngularJS and ngMock.
Common Pitfalls
One common mistake is forgetting $httpBackend.flush(), which leaves the promise unresolved and makes the result look undefined.
Another pitfall is treating $httpBackend as if it were a real time-delay simulator. It is a deterministic transport mock, not an autonomous server.
A third issue is mismatching the expected URL, method, or payload. In that case, the test fails because the mock setup is wrong, not because the application logic is wrong.
Finally, if the test depends on watcher-driven updates beyond the HTTP promise chain, flushing the backend alone may not be the whole story.
Summary
- '
$httpBackendresponds only when the test callsflush().' - The application code remains asynchronous in structure, but the test controls the timing deterministically.
- Define the expectation, trigger the request, then flush the backend.
- Use verification helpers after each test to catch unresolved or mismatched requests.
- '
$httpBackendis the right mental model for AngularJS unit tests, but it is a mock backend, not a real server.'

