Угловой транспортир e2e Тестирование

Я пишу сквозной тест с использованием Protractor для моего приложения Angular. Я могу смоделировать httpBackend для модульного тестирования, но я хочу на самом деле вызвать сервер и получить ответ JSON и снова написать тесты для возвращаемых данных.
Я много читал о stackru, но не могу понять, как это сделать.

Я использую $http? Как мне ввести его в мои тесты Жасмин? Как мне получить ответ JSON обратно в мой тест Жасмин?

любая помощь или ссылки на ресурсы с инструкциями по выполнению этого будут полезны.

Опять же, я НЕ хочу издеваться над сервером, я хочу попасть на сервер и вернуть JSON.

Спасибо!

4 ответа

Я работаю через это сам в данный момент. Короткий ответ, который я считаю, заключается в том, что вы настроили свое приложение точно так же, как если бы вы сами тестировали его вручную, поэтому Protractor действительно просто пользователь робота, у него нет (ну, почти нет) доступа к внутренним компонентам вашего приложения.

Таким образом, если вашему приложению требуется веб-сервер (и большинство этого требуют), вы запускаете этот веб-сервер, а затем подключаете транспортир к вашему приложению через браузер и выполняете его.

В моем случае я собираюсь использовать grunt для вызова задачи, которая выполняет базовую настройку базы данных, прежде чем она начнет выполнять мои тесты транспортира e2e - это должно дать мне известное состояние базы данных.

В качестве примера этого я написал учебник по использованию Rails 4 с AngularJS, раздел об использовании транспортира для тестирования e2e не зависит от rails и может быть полезен: http://technpol.wordpress.com/2013/11/16/5-end-to-end-testing/

Транспортир должен использоваться для сквозного тестирования вашего полного стека.

В этом сценарии тест обычно выполняет угловое приложение (заполнение формы, нажатие кнопок), которое запускает угловое приложение для вызова на REST-сервер, который возвращает данные, которые ваше угловое приложение преобразует в изменения DOM, которые затем становятся вашими сквозными. тест завершается.

Это означает, что вы, вероятно, захотите запустить свой сервер приложений (на котором размещено угловое приложение и я полагаю, что это REST-сервер) перед запуском Protractor

Как это сделать, выходит за рамки Транспорта.

Сложность в этом, как правило, заключается в том, как настроить вашу базу данных, чтобы тест e2e знал, чего ожидать в качестве возврата к вашим службам JSON.

Ниже приведен пример того, как автоматически запускать и останавливать отдельный сервер узлов только во время выполнения тестов e2e. Простой пример скриптового серверного скрипта включен в качестве примера API.

protractor.conf.js

const {SpecReporter} = require('jasmine-spec-reporter');
const forever = require('forever-monitor');

const child = new (forever.Monitor)('index.js', {
  max: 10,
  silent: false,
  args: ["--port", "3001"],
  sourceDir: 'mock-server'
});

let startResolve;
let stopResolve;
const startPromise = new Promise((resolve) => startResolve = resolve);
const stopPromise = new Promise((resolve) => stopResolve = resolve);

child.on('start', function () {
  console.info('Forever started mocks.');
  startResolve();
});

child.on('restart', function () {
  console.info('Forever restarting mocks for ' + child.times + ' time');
});

child.on('exit:code', function (code) {
  if (code) {
    console.info('Forever exit mocks with code ' + code);
  } else {
    console.info('Forever exited mocks.');
  }
  stopResolve();
});

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function () {
    }
  },
  beforeLaunch: function () {
    child.start();

    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });

    return startPromise;
  },
  onPrepare() {
    jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}}));
  },
  onCleanUp() {
    child.stop();

    return stopPromise;
  }
};

макет сервера /index.js

// npm install --save express
// npm install --save body-parser
// npm install --save minimist

const express = require('express');
const bodyParser = require('body-parser');
const minimist = require('minimist');

const API_DELAY = 0;
const app = express();
app.use(bodyParser.json({limit: '50mb'}));

// Turn on CORS for browser testing.
app.use(function (req, res, next) {
  let accessHeaderInReq = false;
  if (req.headers.origin) {
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    accessHeaderInReq = true;
  }
  if (req.headers['access-control-request-method']) {
    res.header('Access-Control-Allow-Methods', req.headers['access-control-request-method']);
    accessHeaderInReq = true;
  }
  if (req.headers['access-control-request-headers']) {
    res.header('Access-Control-Allow-Headers', req.headers['access-control-request-headers']);
    accessHeaderInReq = true;
  }
  if (accessHeaderInReq) {
    res.header('Access-Control-Max-Age', 60 * 60 * 24 * 365);
  }

  // Intercept OPTIONS method for angular preflight checks.
  if (accessHeaderInReq && req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  else {
    next();
  }
});

app.get('/api/foo', function (req, res, next) {
  console.info('GET - returning foo', req.body);
  setTimeout(() => {
    res.json({
      foo: "bar"
    });
  }, API_DELAY);
});

const argv = minimist(process.argv.slice(2));
const port = argv.port || 3000;
console.log("Starting express on port", port);
app.listen(port);

Для сред непрерывной интеграции вы можете установить фиктивный сервер node_modules без изменения каталогов, например:

npm --prefix ./mock-server install ./mock-server

В дополнение к нашему тестированию Protractor e2e мы проводим интеграционное тестирование "напрямую по API и тестируем данные в ответе". Для тестирования на стороне API вам не нужен Protractor, потому что нет необходимости запускать браузер только для отправки HTTP-запросов на сервер.

Вот что мы делаем:

  • Используйте Jasmine напрямую для запуска наших интеграционных тестов API. (Существует пакет jasmine npm, который вы можете установить.) Таким образом, мы поддерживаем знакомый describe()/it()/expect() грамматика из нашей среды транспортира (которая основана на жасмине). Поэтому вместо запуска транспортира для запуска тестов вы запускаете jasmine, а-ля: jasmine --config=jasmine.json path/to/tests/*spec.js
  • Используйте пакет обещание-обещание npm для генерации HTTP-запросов.

Наши spec-файлы выглядят примерно так:

describe('API Tests written in Jasmine', function() {    
  beforeAll(() => authAsAdmin());

  it('Should get a proposal object as auth\'d user', function() {
    const httpOptions = {
      uri: `/proposals/100`,
    };
    return requestWithAuth(httpOptions)
      .then(res => {
        const proposal = res.body.proposal;
        // console.log(`Proposal ${proposal.id} title: ${proposal.title}`);
        expect(proposal.id).toEqual(100);
        expect(res.statusCode).toEqual(200);
        expect(res.statusMessage).toBe('OK');
      });
  });

Наши spec-файлы зависят от некоторых глобальных вспомогательных методов, которые мы установили в вспомогательном файле Jasmine (часть стандартной механики работы Jasmine), как показано ниже:

const rp = require('request-promise');
...
// Declare our helper methods globally so they can be accessed anywhere in tests
global.requestWithAuth = requestWithAuth;
global.authAs = authAs;
global.authAsAdmin = () => authAs(ADMIN_USER);
global.catchErrorInLocation = (error, location) => {
  throw new Error(`Error in ${location}\n     ${error}`);
};
global.catchErrorInBeforeAll = (error) => catchErrorInLocation(error, 'beforeAll()');

function authAs(user) {
  ...
}

/**
 * Combines a given set of options with the DEFAULT_HTTP_OPTIONS plus a session token
 * and initiates an http request, returning a promise for the response.
 * @param {Object} options properties matching request-promise API
 * @param {string} token, optional session token. sessionToken used by default.
 * @returns {Promise} request-promise response
 */
function requestWithAuth(options, token = sessionToken) {
  Object.assign(options, { ...DEFAULT_HTTP_OPTIONS, ...options }); // Merge custom options with default options
  options.headers['x-token'] = token; // Merge current session token into options
  options.uri = `${BASE_URL}${options.uri}`; // Update the URI to include the correct base path

  return rp(options);
}

Надеюсь, это поможет.

Другие вопросы по тегам