As I dive into unit testing for my Angular application, I find myself pondering the best way to organize the tests folder. My project was initialized using the yeoman angular generator, which comes equipped with Jasmine and Karma out of the box.
Let's consider a scenario that I am currently working on...
One of the components in my app is the "PageHeaderDirective" which showcases a user's name and email (essentially a welcome message) along with a logout link. While the logic within the page header directive is not critical, it does require calling the "/user" endpoint from the backend to fetch user details. Below is the snippet of UserService code that gets injected into PageHeaderDirective:
/**
* @ngdoc function
* @name Common.service.UserService
* @description
* Service to retrieve a {@link User} from the backend.
*/
(function () {
'use strict';
angular.module('Common').service('UserService', UserService);
UserService.$inject = ['User', 'Restangular'];
/**
* User service function.
* @param User The {@link User} provider.
* @param Restangular The restangular provider.
*/
function UserService(User, Restangular) {
var userPromise;
return {
getUser: getUser
};
/**
* Retrieves a {@link User} instance from the /user endpoint.
* @returns A promise to be resolved with a {@link User} instance.
*/
function getUser() {
if(!userPromise) {
userPromise = Restangular.one('user').get().then(function(data) {
return User.factory(data);
});
}
return userPromise;
}
}
})();
Here's a straightforward test case for PageHeaderDirective:
describe('Pageheader Tests', function() {
'use strict';
var scope;
var element;
beforeEach(module('templates'));
beforeEach(module('Common'));
beforeEach(inject(function(_$rootScope_, $compile) {
scope = _$rootScope_.$new();
scope.message = 'Test message';
element = '<ft-page-header message="message" page="home"></ft-page-header>';
element = $compile(element)(scope);
scope.$digest();
}));
it('should render a page header with the logo and username', function() {
expect(element.find('.logo-text').length).toBe(1);
var isolateScope = element.isolateScope();
expect(isolateScope.name).toBe('test');
});
});
However, when running these tests, I encounter an unknown provider error stating "Unknown provider: RestangularProvider <- Restangular <- UserService <- pageHeaderDirective" because I haven't injected it into the tests.
I've come across suggestions like
beforeEach(function(){ module(function($provide) { $provide.service('UserService', function() { ... }})});
to inject dependencies in each test file, but I'm exploring ways to centralize this setup by creating a separate "UserService.mock.js" file. How can I achieve this separation and import "UserService.mock.js" into my tests?
Furthermore, I also utilize Restangular within PageHeaderDirective for logging out users (
Restangular.one('logout').get().then...
). Is there a strategy to mock these API calls without actually making requests?
Lastly, considering other providers such as $document, $localStorage, and $window that are injected into components, should they be included in tests as well? If yes, how should this integration be approached?
Your insights are greatly appreciated!