Почему я вижу "define not defined" при выполнении теста Mocha с RequireJS?
Я пытаюсь понять, как разработать автономный код Javascript. Я хочу написать код Javscript с помощью тестов и модулей, запущенных из командной строки. Поэтому я установил node.js
и npm
вместе с библиотеками requirejs
, underscore
и mocha
.
Моя структура каталогов выглядит так:
> tree .
.
├── node_modules
├── src
│ └── utils.js
└── test
└── utils.js
где src/utils.js
- это небольшой модуль, который я пишу, со следующим кодом:
> cat src/utils.js
define(['underscore'], function () {
"use strict";
if ('function' !== typeof Object.beget) {
Object.beget = function (o) {
var f = function () {
};
f.prototype = o;
return new f();
};
}
});
и test/utils.js
- это тест:
> cat test/utils.js
var requirejs = require('requirejs');
requirejs.config({nodeRequire: require});
requirejs(['../src/utils'], function(utils) {
suite('utils', function() {
test('should always work', function() {
assert.equal(1, 1);
})
})
});
который затем я пытаюсь запустить из каталога верхнего уровня (поэтому mocha
видит каталог test
):
> mocha
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: Calling node require("../src/utils") failed with error: ReferenceError: define is not defined
at /.../node_modules/requirejs/bin/r.js:2276:27
at Function.execCb (/.../node_modules/requirejs/bin/r.js:1872:25)
at execManager (/.../node_modules/requirejs/bin/r.js:541:31)
...
Итак, мои вопросы:
- Это правильный способ структурирования кода?
- Почему мой тест не работает?
- Каков наилучший способ узнать такие вещи? Мне сложно найти хорошие примеры с Google.
Спасибо...
[извините - моментально вывел результаты из-за неправильного кода; теперь исправлено]
PS Я использую requirejs, потому что я также хочу запустить этот код (или его часть) из браузера позже.
Обновление/решение
Что-то, чего нет в ответах ниже, заключается в том, что мне нужно было использовать mocha -u tdd
для тестового стиля выше. Вот окончательный тест (который также требует утверждения) и его использование:
> cat test/utils.js
var requirejs = require('requirejs');
requirejs.config({nodeRequire: require});
requirejs(['../src/utils', 'assert'], function(utils, assert) {
suite('utils', function() {
test('should always work', function() {
assert.equal(1, 1);
})
})
});
> mocha -u tdd
.
✔ 1 tests complete (1ms)
Ответы
Ответ 1
Причина, по которой ваш тест не выполняется, заключается в том, что src/utils.js
не является допустимой библиотекой Node.js.
В соответствии с документацией RequireJS, чтобы сосуществовать с Node.js и для CommonJS требуется стандарт, вам нужно добавить немного шаблона в начало вашего файла src/utils.js
, чтобы RequireJS define
Функция загружена.
Однако, поскольку RequireJS был спроектирован так, чтобы требовать "классический" исходный код, ориентированный на веб-браузер, я склонен использовать следующий шаблон с моими библиотеками Node.js, которые я также хочу запускать в браузере:
if(typeof require != 'undefined') {
// Require server-side-specific modules
}
// Insert code here
if(typeof module != 'undefined') {
module.exports = whateverImExporting;
}
Это имеет то преимущество, что не требует дополнительной библиотеки для других пользователей Node.js и в целом хорошо работает с RequireJS на клиенте.
Как только вы запустите свой код в Node.js, вы можете начать тестирование. Лично я все еще предпочитаю экспрессо, а не мокко, хотя это тестовая структура преемника.
Ответ 2
В документации по Mocha не хватает того, как установить этот материал, и он с трудом понимает, из-за всех волшебных трюков, которые он делает под капотом.
Я нашел ключи для получения файлов браузера с использованием require.js для работы в Mocha в разделе Node: Mocha должен иметь файлы, добавленные в свои пакеты с помощью addFile
:
mocha.addFile('lib/tests/Main_spec_node');
И во-вторых, используйте beforeEach
с дополнительным обратным вызовом для асинхронной загрузки ваших модулей:
describe('Testing "Other"', function(done){
var Other;
beforeEach(function(done){
requirejs(['lib/Other'], function(_File){
Other = _File;
done(); // #1 Other Suite will run after this is called
});
});
describe('#1 Other Suite:', function(){
it('Other.test', function(){
chai.expect(Other.test).to.equal(true);
});
});
});
Я создал бутстрап для того, как все это работает: https://github.com/clubajax/mocha-bootstrap
Ответ 3
Вы пытаетесь запустить JS-модули, предназначенные для браузеров (AMD), но в бэкэнд он может не работать (поскольку модули загружаются обычным способом). Из-за этого вы столкнетесь с двумя проблемами:
- define не определен
- 0 тестов
В браузере define
будет определено. Он будет установлен, когда вам потребуется что-то с requirejs. Но nodejs загружает модули методом commonjs. define
в этом случае не определен. Но он будет определен, когда мы потребуем с requirejs!
Это означает, что теперь мы требуем код асинхронно, и это приносит вторую проблему, проблему с выполнением async.
https://github.com/mochajs/mocha/issues/362
Вот полный рабочий пример.
Посмотрите, что мне пришлось настроить requirejs (amd) на загрузку модулей, мы не используем require (node/commonjs) для загрузки наших модулей.
> cat $PROJECT_HOME/test/test.js
var requirejs = require('requirejs');
var path = require('path')
var project_directory = path.resolve(__dirname, '..')
requirejs.config({
nodeRequire: require,
paths: {
'widget': project_directory + '/src/js/some/widget'
}
});
describe("Mocha needs one test in order to wait on requirejs tests", function() {
it('should wait for other tests', function(){
require('assert').ok(true);
});
});
requirejs(['widget/viewModel', 'assert'], function(model, assert){
describe('MyViewModel', function() {
it("should be 4 when 2", function () {
assert.equal(model.square(2),4)
})
});
})
И для модуля, который вы хотите проверить:
> cat $PROJECT_HOME/src/js/some/widget/viewModel.js
define(["knockout"], function (ko) {
function VideModel() {
var self = this;
self.square = function(n){
return n*n;
}
}
return new VideModel();
})
Ответ 4
На всякий случай ответ Дэвида не был достаточно ясен, мне просто нужно было добавить это:
if (typeof define !== 'function') {
var define = require('amdefine')(module);
}
В начало js файла, где я использую define
, как описано в документах RequireJS ( "Создание node модулей с AMD или RequireJS" ) и в той же папке добавьте пакет amdefine
:
npm install amdefine
Это создает папку node_modules
с внутренним модулем amdefine
.
Ответ 5
Я не использую requirejs
, поэтому я не уверен, как выглядит этот синтаксис, но это то, что я делаю для запуска кода как внутри node
, так и browser
:
Для импорта определите, запущены ли мы в node или в браузере:
var root = typeof exports !== "undefined" && exports !== null ? exports : window;
Затем мы можем правильно захватить любые зависимости (они будут либо уже доступны, если в браузере, либо мы используем require
):
var foo = root.foo;
if (!foo && (typeof require !== 'undefined')) {
foo = require('./foo');
}
var Bar = function() {
// do something with foo
}
И затем любую функциональность, которая должна использоваться другими файлами, мы экспортируем ее в root:
root.bar = Bar;
Как для примеров, GitHub
является отличным источником. Просто зайдите и проверьте код своей любимой библиотеки, чтобы увидеть, как они это сделали:) Я использовал mocha
для проверки библиотеки javascript, которая может использоваться как в браузере, так и в node. Код доступен в https://github.com/bunkat/later.