Угловой транспортир 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);
}
Надеюсь, это поможет.