33

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

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

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

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

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

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

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

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

4

8 回答 8

10

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

于 2014-05-03T02:38:46.137 回答
4

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

  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的更深入的帖子

于 2014-08-21T02:51:57.020 回答
2

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

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

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

beforeEach(function() {
  $httpBackend.whenGET('/remote-url').respond(edgeCaseData);
});
于 2014-03-27T09:17:16.243 回答
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');
    }
};
于 2014-07-13T10:30:21.043 回答
1

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

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

于 2014-04-10T19:28:14.700 回答
0

使用量角器运行端到端测试的目的是验证应用程序是否在集成中工作。如果您尝试单独测试您的 ui 元素,则使用普通测试中的小元素会更容易。就像 AngularJS 本身测试指令一样。

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

于 2014-03-21T19:29:03.573 回答
0

以下是存根 HTTP 服务器的更多选项:

  • Stubby一个支持 node、.Net 和 Java的小型Web 服务器。您将需要它来安装和托管自己。
  • Apiary一个用于创建虚假 API的托管服务。您也可以使用它来创建 API 文档。
于 2014-07-15T15:23:44.013 回答
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,同时)

模拟服务器.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 客户端名称(作为 API 方法调用的 e2e 规范中使用的唯一名称声明)相关的 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"

端到端使用

在启动 mock api 服务器之前,应将所有 mock 文件声明为 *.mock.json 并预设为 *.preset.json,以处理所有 mock 和预设。可以通过在 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 文档

于 2021-04-18T13:09:06.223 回答