Open clarkmalmgren opened 8 years ago
I agree that tests in Angular that uses promises are the most painful to test and I would favor a simpler way to test it. However you can cut down on some of the boiler plate code and have it similar to below. Or put it in a separate mock service as you propose.
describe('on activate', function() {
it('sets the user when service call succeeds', function() {
var list = {};
var deferred = $q.defer();
spyOn(UserService, 'list').and.returnValue(deferred.promise);
deferred.resolve(list);
controller.activate();
scope.$digest();
expect(controller.list).toBe(list);
});
activate testing is interesting. ideally we'd have an activate event so we can test before it happens. but we do not.
i'm curious to see where this issue leads. comments welcome
@clarkmalmgren
I don't see what you are testing in your controller. There is no logic in it.
For me, you are just testing that vm.users = users;
, so the answer here is : this unit test is irrelevant. Just a waste of time.
Anyway, ng-describe is really good to avoid boilerplate. Love it !
@MarcLoupias,
For this example, you are right, I am only testing that the Service is actually setting the appropriate value. But in practice, there is almost always more complex code in the activate path. For my internal specific use case that prompted this post I was doing both:
Also, just because the assertion is easy to write doesn't necessarily discount the utility of the unit test. This isn't a getter/setter test. This is ensuring that a Service call is invoked as expected and that the chained response (or part of the response) ends up being populated in the right place.
PR anyone?
Background
Following the style guide is great although there is a bit of a problem around the way a controller's
activate()
function works in practice. Let's say that I have a controller for populating users. Looks nice:File:
user_list.controller.js
The Problem
The problem occurs when you try to test it. Your unit tests end up looking unwieldy due to the fact that the boilerplate code to get this setup. This is actually because what we have here is a testability flaw (see Flaw: Constructor does Real Work).
Here is an example with all of the boilerplate code: File:
user_list.controller.spec.js
Note that for this test, there are nearly 15 lines of boilerplate code. Furthermore, this boilerplate code (especially the mock objects) are not shareable. There are several solutions to reduce the boilerplate code including ng-describe. But you still have a problem with repeated code in your unsharable mocks.
Proposed Solution
"appName.mock"
module{{service_name}}.service.mock.js
.activate()
method to thevm
variable so that activate may be called after construction.Example
File:
user.service.mock.js
This can now be used in any test that talks to a UserService.
File:
user_list.controller.spec.js
Note that this gets even better when you use tools like ng-describe.
The other thing that isn't shown here is that when I have a test for a function that gets called post-construction, I don't have to write any additional code related to the calling of the
activate()
method, I can just inject my values.