I have a controller inside angularjs that gets an Analytics service injected into it. This is just an interface to interact to Google Analytics (though this service is not specific to this question).
The $scope provides a method that reacts to a user interaction (i.e. the user clicks continue). Once this happens, Analytics.event function is called (to send an event to GA), and the other application specific logic occurs.
Example (fiddle here):
app.controller("MainController", function ($scope, Analytics, EmployeeRepository, EmployeeValidator) {
$scope.employee = {};
$scope.showError = false;
$scope.clickContinue = function (employee) {
if (EmployeeValidator.isValid(employee)) {
$scope.showError = false;
Analytics.event("User submitted valid data").then(function () {
EmployeeRepository.saveEmployee(employee);
}).then(function () {
//navigate to next view
});
return;
}
//not valid
$scope.showError = true;
Analytics.event("User submitted invalid data");
};
});
For the above code I would have unit tests that would look something like this:
describe("myApp", function () {
var $scope;
var Analytics;
var EmployeeRepository;
var EmployeeValidator;
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, $q, _Analytics_, _EmployeeRepository_, _EmployeeValidator__) {
$scope = $rootScope.$new();
Analytics = _Analytics_;
EmployeeRepository = _EmployeeRepository_;
EmployeeValidator = _EmployeeValidator_;
//Mock out the calls controller uses
spyOn(Analytics, "event").andReturn($q.when());
spyOn(EmployeeRepository, "saveEmployee").andReturn($q.when());
spyOn(EmployeeValidator, "isValid").andReturn(true);
$controller("MainController", {$scope: $scope});
}));
//Are these specs right? Or should they be more granular?
//E.g. breaking out into many specs:
// Event on validation success
// saveEmployee is called on validation success
// Event on validation failure
// $scope.showError true on validation failure
// etc.
it("should save employee on validation success", function () {
//Assemble
var ee = {};
EmployeeValidator.isValid.andReturn(true);
//Act
$scope.clickContinue(ee);
$scope.$digest();
//Assert
expect($scope.showError).toBe(false);
expect(AnalyticsService.event).toHaveBeenCalledWith("User submitted valid data");
expect(EmployeeRepository.saveEmployee).toHaveBeenCalledWith(ee);
//expect next view to show...
});
it("should not save employee on validation failure", function () {
//Assemble
var ee = {};
EmployeeValidator.isValid.andReturn(false);
//Act
$scope.clickContinue(ee);
$scope.$digest();
//Assert
expect($scope.showError).toBe(true);
expect(AnalyticsService.event).toHaveBeenCalledWith("User submitted invalid data");
expect(EmployeeRepository.saveEmployee).not.toHaveBeenCalled();
//expect next view NOT to show...
});
});
Is it valid to only have two specs (e.g. it should) here like above? Should the specs be more granular to expect that only one piece of the entire function works when clickContinue is called (i.e. a spec just for $scope.showError to be set based on EmployeeValidation)?
In this simplest case, the controller has 4 dependencies. In actual code, this grows and I feel like the controller's logic gets to be bloated. Is the controller the correct place to be submitting Analtyics events through the Analytics service? Is all this logic violating the Single Responsibility Principle?
Thanks in advance for any help or suggestions.
Aucun commentaire:
Enregistrer un commentaire