如何使用Jasmine测试Angular App的服务

时间:2022-08-24 22:30:14

I'm practicing Angular and automated testing.

我正在练习Angular和自动化测试。

I made a simple app to tell the user the strength of their password. Originally everything was within the PasswordController and used $scope. Then I moved the logic into a service. The controller and service are currently this:

我做了一个简单的应用程序来告诉用户他们的密码的强度。最初一切都在PasswordController中并使用了$ scope。然后我将逻辑移到服务中。控制器和服务目前是这样的:

myApp.controller("PasswordController", function ($scope, PasswordService) {

  $scope.users = PasswordService.list();

  $scope.saveUser = function () {
    PasswordService.save($scope.newUser);
    $scope.newUser = {};
  }

  $scope.delete = function (id) {
    PasswordService.delete(id);
    if($scope.newUser.id == id){
      $scope.newUser = {};
    }
  }

  $scope.edit = function(id) {
    $scope.newUser = angular.copy(PasswordService.get(id));
  }
});

myApp.service('PasswordService', function() {

  //user with unique id
  var uid = 1;

  //user array
  var users = [
    {
      id: 0,
      'name': 'a_test_name',
      'password': 'a_test_pw',
      'pwStrength': 'medium'
    }
  ];

  this.save = function(user) {
    if (user.id == null) {
      user.id = uid++;
      this.grade(user);
      users.push(user);
    }
    else {
      for (i in users) {
        if (users[i].id == user.id) {
          users[i] = user;
          this.grade(user);
        }
      }
    }
  }

  this.get = function(id) {
    for (i in users) {
      if (users[i].id == id) {
        return users[i];
      }
    }
  }

  this.delete = function(id) {
    for (i in users) {
      if (users[i].id == id) {
        users.splice(i, 1);
      }
    }
  }

  this.list = function () {
    return users;
  }

  this.grade = function(user) { 
    var size = user.password.length;
    if (size > 8) {
      user.pwStrength = "strong";
    }
    else if (size > 3) {
      user.pwStrength = "medium";
    }
    else {
      user.pwStrength = "weak";
    }
  }
});

My test cases had all been working when everything was in the PasswordController but I can't figure out how to set up the test cases to use the service. This is the test code without modifications: (so some of the variable names don't quite line up)

当所有内容都在PasswordController中时,我的测试用例一直在工作,但我无法弄清楚如何设置测试用例来使用该服务。这是没有修改的测试代码:(因此一些变量名称不完全对齐)

describe('PasswordController', function() {
  beforeEach(module('myApp'));

  var $controller;
  var $scope;
  var controller;

  beforeEach(inject(function(_$controller_){
    // The injector unwraps the underscores (_) from around the parameter names when matching
    $controller = _$controller_;
  }));

  describe("$scope.grade", function() {

    beforeEach(function() {
      $scope = {};
      controller = $controller("PasswordController", { $scope: $scope });
    });

    it("method should exist", function() {
      expect($scope.grade).toBeDefined();
    });

    it('sets the strength to "strong" if the password length is >8 chars', function() {
      $scope.password = 'longerthaneightchars';
      $scope.grade();
      expect($scope.strength).toEqual('strong');
    });

    it('strength to "medium" if the password length is >8 chars && <3 chars', function() {
      $scope.password = 'between';
      $scope.grade();
      expect($scope.strength).toEqual('medium');
    });

    it('sets the strength to "weak" if the password length <3 chars', function() {
      $scope.password = 'a';
      $scope.grade();
      expect($scope.strength).toEqual('weak');
    });
  });
});

I can't figure out how I'd convert the test cases to use the PasswordService. I've tried to inject the PasswordService into the controller and tried to use spyOn to mock the service and use it that way but so far no luck.

我无法弄清楚如何将测试用例转换为使用PasswordService。我试图将PasswordService注入控制器,并尝试使用spyOn来模拟服务并以这种方式使用它,但到目前为止还没有运气。

  beforeEach(module('myApp', function ($provide) {
    PasswordService = jasmine.createSpyObj("PasswordService", ["save" ...]);

controller = $controller("PasswordController", { $scope: $scope, PasswordSerice: PasswordService });

Any hints of links to tutorials are appreciated. Thanks much.

任何教程链接的提示都表示赞赏。非常感谢。

1 个解决方案

#1


1  

Since you separate the controller and the service (what was probably a good thing), now it also makes sense to separate the tests.

由于您将控制器和服务分开(这可能是一件好事),现在将测试分开也是有意义的。

You should create one test for the controller. The test will aim for the methods inside the controller only (save, delete and edit).

您应该为控制器创建一个测试。测试将仅针对控制器内部的方法(保存,删除和编辑)。

And then you create another test for the service which will cover the methods inside the service.

然后,您将为服务创建另一个测试,该测试将涵盖服务中的方法。

I will create an example example for both scenarios to help you out:

我将为两个场景创建一个示例示例来帮助您:

describe('PasswordController Specification', function() {
  beforeEach(module('myApp'));

  var myPasswordController;

  beforeEach(inject(function(_$controller_, $rootScope){
    myPasswordController= _$controller_('PasswordController', {
         $scope: $rootScope.$new();
    });
  }));

  it("Should save the user", inject(function(PasswordService) {
    //setup
    var myFakeUser = {id: 1, name: 'whatever'};
    myPasswordController.newUser = myFakeUser;

    /*
    Here I am spying the save method. This is necessary to check if it was 
    called. It will actually replace the original implementation with a 
    mocked empty function. And this is right because the controller  
    shouldn't if the service is doing what is supposed to. This is up for 
    the service test.
    */
    spyOn(PasswordService, 'save');

    //action
    myPasswordController.saveUser(myMockedUser);

    //assertion
    expect(PasswordService.save).toHaveBeenCalled();
    expect(myPasswordController.newUser).toBe({});
  }));
});

And the service test:

和服务测试:

describe('PasswordService Specification', function() {
  beforeEach(module('myApp'));

  var myPasswordService;

  beforeEach(inject(function(PasswordService){
    myPasswordService = PasswordService;
  }));

  it("Should save new user", inject(function() {
    //setup
    var numberOfUsers= myPasswordService.list().length;
    var newUser = {
        'name': 'a_test_name',
        'password': 'a_test_pw',
        'pwStrength': 'medium'
    }
    /*
    Here I am spying the grade method. This is necessary to check if it was 
    called. It will actually replace the original implementation with a 
    mocked empty function. And this is right because we are only concerned
    about the save method now.
    */
    spyOn(myPasswordService, 'grade');

    //action
    myPasswordService.save(newUser);

    //assertion
    expect(myPasswordService.grade).toHaveBeenCalled();
    expect(myPasswordService.list().length).toBe(numberOfUsers + 1);
  }));
});

There are other things that you could test but I hope this gives you an insight to start your work

还有其他一些你可以测试的东西,但我希望这能让你有机会开始你的工作

#1


1  

Since you separate the controller and the service (what was probably a good thing), now it also makes sense to separate the tests.

由于您将控制器和服务分开(这可能是一件好事),现在将测试分开也是有意义的。

You should create one test for the controller. The test will aim for the methods inside the controller only (save, delete and edit).

您应该为控制器创建一个测试。测试将仅针对控制器内部的方法(保存,删除和编辑)。

And then you create another test for the service which will cover the methods inside the service.

然后,您将为服务创建另一个测试,该测试将涵盖服务中的方法。

I will create an example example for both scenarios to help you out:

我将为两个场景创建一个示例示例来帮助您:

describe('PasswordController Specification', function() {
  beforeEach(module('myApp'));

  var myPasswordController;

  beforeEach(inject(function(_$controller_, $rootScope){
    myPasswordController= _$controller_('PasswordController', {
         $scope: $rootScope.$new();
    });
  }));

  it("Should save the user", inject(function(PasswordService) {
    //setup
    var myFakeUser = {id: 1, name: 'whatever'};
    myPasswordController.newUser = myFakeUser;

    /*
    Here I am spying the save method. This is necessary to check if it was 
    called. It will actually replace the original implementation with a 
    mocked empty function. And this is right because the controller  
    shouldn't if the service is doing what is supposed to. This is up for 
    the service test.
    */
    spyOn(PasswordService, 'save');

    //action
    myPasswordController.saveUser(myMockedUser);

    //assertion
    expect(PasswordService.save).toHaveBeenCalled();
    expect(myPasswordController.newUser).toBe({});
  }));
});

And the service test:

和服务测试:

describe('PasswordService Specification', function() {
  beforeEach(module('myApp'));

  var myPasswordService;

  beforeEach(inject(function(PasswordService){
    myPasswordService = PasswordService;
  }));

  it("Should save new user", inject(function() {
    //setup
    var numberOfUsers= myPasswordService.list().length;
    var newUser = {
        'name': 'a_test_name',
        'password': 'a_test_pw',
        'pwStrength': 'medium'
    }
    /*
    Here I am spying the grade method. This is necessary to check if it was 
    called. It will actually replace the original implementation with a 
    mocked empty function. And this is right because we are only concerned
    about the save method now.
    */
    spyOn(myPasswordService, 'grade');

    //action
    myPasswordService.save(newUser);

    //assertion
    expect(myPasswordService.grade).toHaveBeenCalled();
    expect(myPasswordService.list().length).toBe(numberOfUsers + 1);
  }));
});

There are other things that you could test but I hope this gives you an insight to start your work

还有其他一些你可以测试的东西,但我希望这能让你有机会开始你的工作