Mein Verständnis ist, dass, wenn Sie Ihr Modul in Angular Unit Tests laden, der run
Block aufgerufen wird.Wie sollte der Laufblock in Angular Unit Tests behandelt werden?
Ich würde denken, dass, wenn Sie eine Komponente zu test, würden Sie nicht gleichzeitig den run
Block testen wollen, weil Einheit Tests sollen nur testen, eine Einheit. Ist das wahr?
Wenn ja, gibt es eine Möglichkeit zu verhindern, dass der run
Block läuft? Meine Forschung führt mich zu der Annahme, dass die Antwort "Nein" ist und dass der Block run
immer ausgeführt wird, wenn das Modul geladen wird, aber vielleicht gibt es eine Möglichkeit, dies zu überschreiben. Wenn nicht, wie würde ich den run
Block testen?
Run Block:
function run(Auth, $cookies, $rootScope) {
$rootScope.user = {};
Auth.getCurrentUser();
}
Auth.getCurrentUser:
getCurrentUser: function() {
// user is logged in
if (Object.keys($rootScope.user).length > 0) {
return $q.when($rootScope.user);
}
// user is logged in, but page has been refreshed and $rootScope.user is lost
if ($cookies.get('userId')) {
return $http.get('/current-user')
.then(function(response) {
angular.copy(response.data, $rootScope.user);
return $rootScope.user;
})
;
}
// user isn't logged in
else {
return $q.when({});
}
}
auth.factory.spec.js
describe('Auth Factory', function() {
var Auth, $httpBackend, $rootScope, $cookies, $q;
var user = {
username: 'a',
password: 'password',
};
var response = {
_id: 1,
local: {
username: 'a',
role: 'user'
}
};
function isPromise(el) {
return !!el.$$state;
}
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('#signup', function() {
$rootScope.user = {};
$httpBackend.expectPOST('/users', user).respond(response);
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'put').and.callThrough();
var retVal = Auth.signup(user);
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect($cookies.put).toHaveBeenCalledWith('userId', 1);
expect(isPromise(retVal)).toBe(true);
});
it('#login', function() {
$rootScope.user = {};
$httpBackend.expectPOST('/login', user).respond(response);
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'put').and.callThrough();
var retVal = Auth.login(user);
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect($cookies.put).toHaveBeenCalledWith('userId', 1);
expect(isPromise(retVal)).toBe(true);
});
it('#logout', function() {
$httpBackend.expectGET('/logout').respond();
spyOn(angular, 'copy').and.callThrough();
spyOn($cookies, 'remove');
Auth.logout();
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith({}, $rootScope.user);
expect($cookies.remove).toHaveBeenCalledWith('userId');
});
describe('#getCurrentUser', function() {
it('User is logged in', function() {
$rootScope.user = response;
spyOn($q, 'when').and.callThrough();
var retVal = Auth.getCurrentUser();
expect($q.when).toHaveBeenCalledWith($rootScope.user);
expect(isPromise(retVal)).toBe(true);
});
it('User is logged in but page has been refreshed', function() {
$cookies.put('userId', 1);
$httpBackend.expectGET('/current-user').respond(response);
spyOn(angular, 'copy').and.callThrough();
var retVal = Auth.getCurrentUser();
$httpBackend.flush();
expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
expect(isPromise(retVal)).toBe(true);
});
it("User isn't logged in", function() {
$rootScope.user = {};
$cookies.remove('userId');
spyOn($q, 'when').and.callThrough();
var retVal = Auth.getCurrentUser();
expect($q.when).toHaveBeenCalledWith({});
expect(isPromise(retVal)).toBe(true);
});
});
});
Versuch 1:
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
beforeEach(function() {
spyOn(Auth, 'getCurrentUser');
});
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Dies funktioniert nicht. Der Baustein run
wird ausgeführt, wenn das Modul geladen wird. Daher wird Auth.getCurrentUser()
aufgerufen, bevor der Spion eingerichtet wird.
Expected spy getCurrentUser to have been called.
Versuch 2:
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
beforeEach(function() {
spyOn(Auth, 'getCurrentUser');
});
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Das funktioniert nicht, weil Auth
nicht verfügbar ist injiziert werden, bevor meine App-Modul geladen wird.
Error: [$injector:unpr] Unknown provider: AuthProvider <- Auth
Versuch 3:
Wie Sie sehen können, gibt es ein hier Huhn-Ei-Problem. Ich muss Auth injizieren und den Spion einrichten, bevor das Modul geladen wird, aber ich kann nicht, da Auth nicht verfügbar ist, um injiziert zu werden, bevor das Modul geladen wird.
This Blog-Beiträge erwähnt das Huhn-Ei-Problem und bietet eine interessante potenzielle Lösung. Der Autor schlägt vor, dass ich meinen Auth
Dienst manuell unter Verwendung $provide
vor ich mein Modul laden sollte. Da ich den Dienst erstelle und ihn nicht einspeise, könnte ich es tun, bevor das Modul geladen wird, und ich könnte den Spion einrichten. Wenn dann das Modul geladen wird, würde es diesen erstellten Scheindienst verwenden.
Hier ist sein Beispiel-Code:
describe('example', function() {
var loggingService;
beforeEach(function() {
module('example', function ($provide) {
$provide.value('loggingService', {
start: jasmine.createSpy()
});
});
inject(function (_loggingService_) {
loggingService = _loggingService_;
});
});
it('should start logging service', function() {
expect(loggingService.start).toHaveBeenCalled();
});
});
Das Problem dabei ist, dass ich meine Auth
Service brauchen! Ich würde nur den Schein für den run
Block verwenden wollen; Ich brauche meinen echten Auth
Service woanders, damit ich es testen kann.
Ich denke, dass ich den tatsächlichen Auth
Service mit $provide
erstellen könnte, aber das fühlt sich falsch an.
Letzte Frage - gleich aus welchem Code, den ich am Ende mit diesem run
Block Problem zu umgehen, gibt es eine Möglichkeit für mich, um sie zu extrahieren, so habe ich es nicht neu schreiben für jede meines Spezifikationsdateien? Die einzige Möglichkeit, dies zu tun, wäre eine Art globaler Funktion.
auth.factory.js
angular
.module('mean-starter')
.factory('Auth', Auth)
;
function Auth($http, $state, $window, $cookies, $q, $rootScope) {
return {
signup: function(user) {
return $http
.post('/users', user)
.then(function(response) {
angular.copy(response.data, $rootScope.user);
$cookies.put('userId', response.data._id);
$state.go('home');
})
;
},
login: function(user) {
return $http
.post('/login', user)
.then(function(response) {
angular.copy(response.data, $rootScope.user);
$cookies.put('userId', response.data._id);
$state.go('home');
})
;
},
logout: function() {
$http
.get('/logout')
.then(function() {
angular.copy({}, $rootScope.user);
$cookies.remove('userId');
$state.go('home');
})
.catch(function() {
console.log('Problem logging out.');
})
;
},
getCurrentUser: function() {
// user is logged in
if (Object.keys($rootScope.user).length > 0) {
return $q.when($rootScope.user);
}
// user is logged in, but page has been refreshed and $rootScope.user is lost
if ($cookies.get('userId')) {
return $http.get('/current-user')
.then(function(response) {
angular.copy(response.data, $rootScope.user);
return $rootScope.user;
})
;
}
// user isn't logged in
else {
return $q.when({});
}
}
};
}
Bearbeiten - gescheiterten Versuch + erfolgreichen Versuch:
beforeEach(module('auth'));
beforeEach(inject(function(_Auth_) {
Auth = _Auth_;
spyOn(Auth, 'requestCurrentUser');
}));
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
// Auth = _Auth_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$cookies = _$cookies_;
$q = _$q_;
}));
// beforeEach(function() {
// spyOn(Auth, 'getCurrentUser');
// });
afterEach(function() {
expect(Auth.getCurrentUser).toHaveBeenCalled();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Ich bin nicht sicher, warum dies nicht funktionieren würde (unabhängig von dem Problem mit der Verwendung von inject
zweimal).
Ich habe versucht, um $provide
zu verwenden, wie das anfänglich hacky/seltsam für mich fühlte. Nachdem ich darüber nachgedacht habe, habe ich jetzt das Gefühl, dass $provide
in Ordnung ist, und dass nach Ihrem Vorschlag, mock-auth
zu verwenden, fantastisch ist !!! Beide haben für mich gearbeitet.
In auth.factory.spec.js
Ich lud gerade das auth
Modul (Ich nenne es auth
, nicht mean-auth
) ohne mean-starter
geladen. Dies hat nicht das Blockproblem run
, weil dieses Modul nicht den run
Blockcode hat, aber es erlaubt mir, meine Auth
Fabrik zu testen. In anderen Ländern, das funktioniert:
beforeEach(module('mean-starter', 'templates', function($provide) {
$provide.value('Auth', {
requestCurrentUser: jasmine.createSpy()
});
}));
Wie funktioniert die fantastische mock-auth
Lösung:
auth.factory.mock.js
angular
.module('mock-auth', [])
.factory('Auth', Auth)
;
function Auth() {
return {
requestCurrentUser: jasmine.createSpy()
};
}
user.service.spec.js
beforeEach(module('mean-starter', 'mock-auth', 'templates'));
off-topic zu Ihrer Frage, aber Sie sollten wissen, dass '.run' nicht auf' $ http' warten, um abzuschließen. Wenn irgendetwas in der App davon abhängt, dass das Ergebnis da ist, hast du eine Wettlaufsituation. Normalerweise würden Sie 'resolve' verwenden, wenn Sie' ngRoute' oder 'ui.router' verwenden. –