用量角器嘲弄和剔骨

时间:2013-09-27 17:04:27

标签: javascript unit-testing angularjs selenium protractor

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

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

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

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

我以某种方式期望我需要一个像sinon.js这样的间谍库,或者这已经包含在量角器中?

编辑:我读到了this issue in the protractor issue tracker,这可能是一种方法。 基本上你在测试中编写了一个模拟模块,它被发送到浏览器/应用程序范围内执行。

编辑:以下是更有希望的问题。第一部分讨论adding Mocks to the Angular App。 第二部分讨论mocking the backend

这看起来非常好,在这种情况下,Angular App将保留其原始形式。但是,目前仅适用于弃用的ng-scenario。

8 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:4)

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

  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

的更深入的帖子

答案 2 :(得分:2)

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

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

所以,从上面的文档页面来看,我怀疑你可以在测试之前使用类似下面的东西

beforeEach(function() {
  $httpBackend.whenGET('/remote-url').respond(edgeCaseData);
});

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

我一直试图在量角器中嘲笑某些服务,在看了一些博客之后,我已经找到了适合我的解决方案。这个想法不是做大量的嘲弄,只是产生一些错误的反应;因为对于灯具我已经在我的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');
    }
};

答案 5 :(得分:0)

使用量角器运行端到端测试的关键是验证应用程序是否在集成中工作。如果你试图单独测试你的ui元素,那么使用普通测试中的小元素会更容易。完全像AngularJS本身测试指令。

也就是说,如果你真的想要模拟,一种方法是使用存根而不是真实服务创建一个单独的应用程序构建。

答案 6 :(得分:0)

以下是一些存根HTTP服务器的选项:

  • Stubby一个小型 Web服务器,支持node,.Net和Java。您将需要它来安装和托管自己。
  • Apiary 托管服务以创建虚假API。您也可以使用它来创建API文档。

答案 7 :(得分:0)

我知道有两个这样的模拟框架可以帮助你。 一个是 ng-apimock,另一个是 json-server

我最近开始使用 ng-apimock API 来模拟一些后端 REST 调用。看起来不错,因为我能够在这个 npm 库中看到一些有趣的功能。在这里,您可以定义和选择场景和预设(多个模拟),并基本配置哪个模拟用于哪个 e2e 测试用例。这基本上意味着通过根据需要提供基本响应数据来对 e2e 测试进行细粒度控制。设置起来并不像互联网上的许多博客所说的那样容易,这一点我可以肯定地确认。但它看起来像是我的用例的解决方案。

除了定义模拟和预设(可选)之外,我基本上还必须设置一个 proxy.conf.json,并且还必须维护一些量角器配置以使用此 API。

基本上,您可以在 e2e 测试运行中准确配置应从哪个 API 端点返回的值,并且甚至可以禁用更改应为每个 e2e 测试用例返回的值。在此 API 中还有一个称为 passThrough 的选项,这意味着您甚至可以选择该方案以确保禁用模拟并将调用转到您真正的 HTTP 后端。

如果您想了解更多详细信息,请告诉我,我可能会为您提供有关如何配置它的详细信息。


更新(长时间发布警报!!):

模拟服务器设置(ng-apimock & protractor with express,并发)

mock-server.js

const express = require('express');
const apimock = require('@ng-apimock/core');
const devInterface = require('@ng-apimock/dev-interface');
const app = express();

app.set('port', (process.env.PORT || 30020));

apimock.processor.process({
    src: 'mockapi',              //name of the folder containing mocks and presets
    watch: true
});

app.use(apimock.middleware);
app.use('/mocking', express.static(devInterface));      //endpoint for UI Dev Interface

app.listen(app.get('port'), function() {
    console.log('mock app-server running on port', app.get('port'));
});

package.json(脚本部分)

    "test:e2e": "ng e2e",
    "start:mockapi": "node mockapi/mock-server.js",
    "e2e": "concurrently -k -s first \"npm run start:mockapi\" \"npm run test:e2e\""

package.json(devDependencies 部分)

    "@ng-apimock/core": "^2.6.0",
    "@ng-apimock/dev-interface": "^1.1.0",
    "@ng-apimock/protractor-plugin": "^1.1.0",
    "ng-apimock": "^1.4.9",
    "concurrently": "^6.0.1",
    "express": "^4.17.1",

mock-proxy.conf.json 在 mocks 文件夹中添加了一个需要的代理配置文件。这是为了确保代理 http 后端调用转到正确的模拟服务器 Url。

为 UI 开发界面添加了额外的端点,可在开发期间用于手动配置场景和与定义的模拟相关的其他详细信息。启动mock api server后,可以启动localhost:30020/mocking。如果我们希望禁用所有模拟场景,可以从 UI 中选择 All to passThrough 选项,并且调用将转到实际的 REST 后端应用服务器。

{
    "/api/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/application-server/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/ngapimock/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/mocking/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    }
}

((注意:我们的 Dev App Server 通常运行在 30020 上))

量角器配置

在 protractor.conf.js 中添加了与 Angular 版本、量角器插件包和使用自定义全局 ngApiMock 客户端名称(在 e2e 规范中用于 API 方法调用的唯一名称声明)相关的 ngApiMock 相关选项。

options: {
            globalName: 'ngApiMockClient',  //this name should be used for declaration of Client in e2e tests
        }

以下选项已删除:

useAllAngular2AppRoots → 删除以避免与 ng-apimock 量角器选项附带的 Angular 版本规范冲突。

baseUrl → 删除以避免与下一步中描述的代理配置发生冲突。

angular.json 更改
添加新的 Webpack DevServer 目标 serve-e2e 以指向 mock-proxy.conf.json。然后在典型的 E2E 测试运行期间调用此目标来代替常规的“服务”目标以启动应用程序。这个新的目标添加确保我们不使用代理配置来触发在开发过程中经常使用的“ng serve”上触发的一般应用程序启动。

            "serve": {
                "builder": "@angular-devkit/build-angular:dev-server",
                "options": {
                    "browserTarget": "tnps-portal:build"
                },
                "configurations": {
                    "production": {
                        "browserTarget": "tnps-portal:build:production"
                    }
                }
            },
            "serve-e2e": {
                "builder": "@angular-devkit/build-angular:dev-server",
                "options": {
                    "browserTarget": "tnps-portal:build",
                    "proxyConfig": "mockapi/mock-proxy.conf.json"
                },
                "configurations": {
                    "production": {
                        "browserTarget": "tnps-portal:build:production"
                    }
                }
            },

并将 serve-e2e 指定为 e2e devServer 目标...

            "e2e": {
                "builder": "@angular-devkit/build-angular:protractor",
                "options": {
                    "protractorConfig": "e2e/protractor.conf.js",
                    "devServerTarget": "tnps-portal:serve-e2e"
                },
                "configurations": {
                    "production": {
                        "devServerTarget": "tnps-portal:serve:production"

E2E 使用

在启动mock api服务器之前,所有mock文件都应该声明为*.mock.json,presets为*.preset.json,用于处理所有mocks和presets。可以通过在 mock-server.js 文件中指定模拟和预设的正则表达式模式来修改此默认配置,例如可以制作 *Mock.json & *Preset.json

import { Client } from '@ng-apimock/base-client';
declare const ngApiMockClient: Client;  // !IMPORTANT: the name of the constant should match the global name defined in protractor.conf.js


it('sample mock test', () => {
ngApiMockClient.selectScenario('sample-mock-name', 'sample-scenario-name',);// sample-mock-name is the name of the mock, and sample-scenario-name is the response scenario name as defined in some sample.mock.json

上面的代码应该为特定的模拟选择场景,这基本上意味着可以为一些特定的用例返回一些特定的数据。此数据也在 sample.mock.json 中的响应下定义如下 -

"name": "sample-mock-name",
"isArray": true,                                                                                
"request": {
        "url": "application-server/actual-endpoint-name",   // Endpoint Url for the Mock Request
        "method": "GET"                    // HTTP call type - GET, POST, etc.
    },
"responses": {
        "fileDataScenario": {
            "file": "../data/sampleData.json",   // returns json Array data from a file
            "default": false
        },
        "emptyListScenario": {
            "data": [{}],                       // returns data as array, "isArray" : true mandatory for the same mock.
            "default": true                     // this scenario's data will be returned if no scenario is selected from E2E Code or /mocking UI.
        }

选择场景就足以测试简单的用例。对于需要确保在运行时从某些特定模拟返回特定场景的更复杂情况,请配置预设如下 -

{
    "name": "sample-preset",
    "mocks": {
        "sample-mock-name": {
            "scenario": "fileDataScenario",
            "delay": 3000
        },
        "sample-mock-name-2": {
            "scenario": "someOtherScenarioFromMock2",
            "delay": 3000
        },
        "sample-mock-name-3": {
            "scenario": "oneMoreScenarioFromMock3",
            "delay": 3000
        }

    },
    "variables": {
        "something": "awesome"
    }
}

在 e2e 规范中

ngApiMockClient.selectPreset('sample-preset');

以上代码块描述了一些常见示例,这些示例可能有助于使用量角器和 ng-apimock 模拟 E2E 测试的 REST 调用。

ng-apimock Docs