用量角器模拟和存根

IT技术 javascript unit-testing angularjs selenium protractor
2021-02-20 18:37:10

我想用量角器测试我的 angular 应用程序。该应用程序有一个与服务器通信的 API module 在这些测试期间,我想模拟这个 Api module。我不想进行完整的集成测试,而是使用来自 API 的预期值对用户输入进行测试。这不仅可以使客户端测试更快,还可以让我测试边缘情况,例如连接错误。

我怎样才能用量角器做到这一点?我刚开始设置集成测试。

我使用了 npm 量角器module,安装了 selenium,调整了默认配置并使用了onProtractorRunner.js来验证我的设置是否有效。

推荐的模拟方式是什么?我认为模拟必须在浏览器中完成,而不是直接在测试文件中完成。我假设测试文件中的命令是特定于量角器的,并将发送给 selenium runner。因此,我无法在会话和测试期间共享 javascript 对象。

我不知何故希望我需要一个像sinon.js这样的间谍库,或者这是否已经包含在量角器中?

编辑:在量角器问题跟踪器中阅读了有关此问题的信息,这可能是一种解决方法。基本上你在测试中编写了一个模拟module,它被发送到浏览器/应用程序范围内执行。

编辑:这里有更多有希望的问题。第一个讨论将 Mocks 添加到 Angular App第二个讨论模拟后端

这看起来非常好,在这种情况下,Angular 应用程序将保持其原始形式。但是,这目前仅适用于已弃用的 ng-scenarios。

6个回答

这篇博文讨论了 Protractor 的高级使用场景。特别是它涵盖addMockModule()了量角器浏览器对象的鲜为人知的 方法。该方法允许您在 Protractor 中创建角度module(即 API module的模拟或存根)并将它们上传到浏览器以替换给定规范或规范集上下文中的实际实现。

您无法从量角器测试中访问 $httpBackend、控制器或服务,因此我们的想法是创建另一个 angular module并在测试期间将其包含在浏览器中。

  beforeEach(function(){
    var httpBackendMock = function() {
      angular.module('httpBackendMock', ['ngMockE2E', 'myApp'])
        .run(function($httpBackend) {
          $httpBackend.whenPOST('/api/packages').respond(200, {} );
        })
    }
    browser.addMockModule('httpBackendMock', httpBackendMock)
  })

ngMockE2E 允许你为你的应用程序创建一个假的后端实现。这是关于该主题的更深入的帖子http://product.moveline.com/testing-angular-apps-end-to-end-with-protractor.html

虽然目前我还没有亲自尝试过,但 Angular 为 E2E 测试提供了一个模拟 $httpBackend:

http://docs.angularjs.org/api/ngMockE2E/service/$httpBackend

因此,从上面的文档页面中,我怀疑您可以在测试之前使用以下内容

beforeEach(function() {
  $httpBackend.whenGET('/remote-url').respond(edgeCaseData);
});
我目前正在尝试在我的 e2e 测试文件中执行此操作,但我不知道如何获取$httpBackend. 因为inject()在量角器中不可用。
2021-04-30 18:37:10
仅当您将 html 设置为具有指向模拟module的 ng-app 时,这才有效。不是解决方案。
2021-05-01 18:37:10
这行不通,您无权访问量角器规范中的 $httpBackend
2021-05-17 18:37:10

我创建了一个可自定义的模拟module来帮助我处理成功和错误的情况,也许它会帮助您更好地组织模拟。

https://github.com/unDemian/protractor-mock

是的,我正在准备一个新版本来统一单元和 e2e 模拟数据。
2021-04-21 18:37:10
作为记录,该module已被其作者弃用。
2021-05-08 18:37:10
这个还维护吗?
2021-05-13 18:37:10

我一直在尝试模拟量角器中的一些服务,在查看了一些博客之后,我找到了一个适合我的解决方案。这个想法不是做大量的模拟,只是产生一些错误响应;因为对于装置,我的 API 服务器中已经有一个后门来填充后端。

此解决方案使用$provide.decorator()来更改某些方法。以下是它在测试中的使用方式:

it('should mock a service', function () {
    app.mock.decorateService({
        // This will return a rejected promise when calling to "user"
        // service "login()" method resolved with the given object.
        // rejectPromise() is a convenience method
        user: app.mock.rejectPromise('login', { type: 'MockError' }),

        // You can decorate the service
        // Warning! This code get's stringified and send to the browser
        // it does not have access to node
        api: function ($delegate, $q) {
            $delegate.get = function () {
                var deferred = $q.defer();

                deferred.resolve({ id: 'whatever', name: 'tess' });

                return defer.promise;
            };

            return $delegate;
        },

        // Internally decorateService converts the function to string
        // so if you prefer you can set an string. Usefull for creating your
        // own helper methods like "rejectPromise()".
        dialog: [
            "function ($delegate, $window) {",
                "$delegate.alert = $window.alert;",
                "return $delegate;",
            "}"
        ].join('\n')
    });

    // ...

    // Important!
    app.mock.clearDecorators();
});

这里的代码:

App.prototype.mock = {
    // This must be called before ".get()"
    decorateService: function (services) {
        var code = [
            'var decorer = angular.module("serviceDecorator", ["visitaste"]);',
            'decorer.config(function ($provide) {'
        ];

        for (var service in services) {
            var fn = services[service];

            if (_.isFunction(fn)) {
                code.push('$provide.decorator("'+ service +'", '+ String(fn) +');');
            } else if (_.isString(fn)) {
                code.push('$provide.decorator("'+ service +'", '+ fn +');');
            }
        }

        code.push('});');

        browser.addMockModule('serviceDecorator', code.join('\n'));
    },
    clearDecorators: function () {
        browser.clearMockModules();
    },
    rejectPromise: function (method, error, delay) {
        return [
            'function ($delegate, $q) {',
                '$delegate.'+ method +' = function () {',
                    'var deferred = $q.defer();',
                    '',
                    'setTimeout(function () {',
                        'deferred.reject('+ JSON.stringify(error) +');',
                    '}, '+ (delay || 200) +');',
                    '',
                    'return deferred.promise;',
                '};',
                '',
                'return $delegate;',
            '}'
        ].join('\n');
    }
};