Je veux d'abord dire que je suis nouveau sur RequireJS et encore plus récent sur Jasmine.
J'ai des problèmes avec SpecRunner et j'ai besoin de JS. J'ai suivi les tutoriels d'Uzi Kilon et Ben Nadel (avec quelques autres) et ils en ont aidé certains mais j'ai toujours des problèmes.
Il semble que, s'il y a une erreur qui est lancée dans le test (je peux penser à une en particulier, une erreur de type), le spec runner html s'affichera. Cela me dit que j'ai des problèmes avec le javascript. Cependant, après avoir corrigé ces erreurs, aucun code HTML ne s'affiche plus. Je n'arrive pas du tout à afficher le lanceur de test. Quelqu'un peut-il trouver quelque chose de mal avec mon code qui pourrait causer ce problème?
Voici mon structure de répertoires:
Root
|-> lib
|-> jasmine
|-> lib (contains all of the jasmine lib)
|-> spec
|-> src
|-> jquery (jquery js file)
|-> require (require js file)
index.html (spec runner) specRunner.js
Voici le SpecRunner (index) HTML:
<!doctype html>
<html lang="en">
<head>
<title>Javascript Tests</title>
<link rel="stylesheet" href="lib/jasmine/lib/jasmine.css">
<script src="lib/jasmine/lib/jasmine.js"></script>
<script src="lib/jasmine/lib/jasmine-html.js"></script>
<script src="lib/jquery/jquery.js"></script>
<script data-main="specRunner" src="lib/require/require.js"></script>
<script>
require({ paths: { spec: "lib/jasmine/spec" } }, [
// Pull in all your modules containing unit tests here.
"spec/notepadSpec"
], function () {
jasmine.getEnv().addReporter(new jasmine.HtmlReporter());
jasmine.getEnv().execute();
});
</script>
</head>
<body>
</body>
</html>
Voici le specRunner.js (config)
require.config({
urlArgs: 'cb=' + Math.random(),
paths: {
jquery: 'lib/jquery',
jasmine: 'lib/jasmine/lib/jasmine',
'jasmine-html': 'lib/jasmine/lib/jasmine-html',
spec: 'lib/jasmine/spec/'
},
shim: {
jasmine: {
exports: 'jasmine'
},
'jasmine-html': {
deps: ['jasmine'],
exports: 'jasmine'
}
}
});
Voici une spécification:
require(["../lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("");
});
});
La source du bloc-notes:
define(['lib/jasmine/src/note'], function (note) {
var notes = [
new note('pick up the kids', 'dont forget to pick up the kids'),
new note('get milk', 'we need two gallons of milk')
];
return {
noteTitles: function () {
var val;
for (var i = 0, ii = notes.length; i < ii; i++) {
//alert(notes[i].title);
val += notes[i].title + ' ';
}
return val;
}
};
});
Et la source de la note (JIC):
define(function (){
var note = function(title, content) {
this.title = title;
this.content = content;
};
return note;
});
Je me suis assuré que, en ce qui concerne l'application, les chemins sont corrects. Une fois que cela fonctionne, je peux jouer avec la configuration de ces chemins afin que ce ne soit pas si dégueu.
J'ai réussi à faire fonctionner cela avec quelques essais et erreurs. Le problème principal était que lorsque vous écrivez des spécifications, vous ne devez pas créer de spécifications, vous devez utiliser define:
Original:
require(["/lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
});
Fonctionne:
define(["lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk ");
});
});
});
Après avoir fait quelques recherches, il est devenu clair que, lorsque vous utilisez RequireJS, tout ce que vous voulez que require () utilise doit être enveloppé dans une définition (cela semble évident maintenant, je suppose). Vous pouvez voir que, dans le fichier specRunner.js, un require est utilisé lors de l'exécution des tests (d'où la nécessité de "définir" les spécifications.
L'autre problème est que, lors de la création de spécifications, les descriptions () ET les () sont nécessaires (et pas seulement les descriptions comme dans l'exemple publié).
Original:
describe("returns titles", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
Fonctionne:
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk ");
});
});
J'ai également changé l'endroit où le testeur existe mais c'était un refactor et n'a pas changé le résultat des tests.
Encore une fois, voici les fichiers et les modifications:
note.js: est resté le même
notepad.js: est resté le même
index.html:
<!doctype html>
<html lang="en">
<head>
<title>Javascript Tests</title>
<link rel="stylesheet" href="lib/jasmine/lib/jasmine.css">
<script data-main="specRunner" src="lib/require/require.js"></script>
</head>
<body>
</body>
</html>
specRunner.js:
require.config({
urlArgs: 'cb=' + Math.random(),
paths: {
jquery: 'lib/jquery',
'jasmine': 'lib/jasmine/lib/jasmine',
'jasmine-html': 'lib/jasmine/lib/jasmine-html',
spec: 'lib/jasmine/spec/'
},
shim: {
jasmine: {
exports: 'jasmine'
},
'jasmine-html': {
deps: ['jasmine'],
exports: 'jasmine'
}
}
});
require(['jquery', 'jasmine-html'], function ($, jasmine) {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function (spec) {
return htmlReporter.specFilter(spec);
};
var specs = [];
specs.Push('lib/jasmine/spec/notepadSpec');
$(function () {
require(specs, function (spec) {
jasmineEnv.execute();
});
});
});
notepadSpec.js:
define(["lib/jasmine/src/notepad"], function (notepad) {
describe("returns titles", function () {
it("something", function() {
expect(notepad.noteTitles()).toEqual("pick up the kids get milk");
});
});
});
Il suffit d'ajouter cela comme une réponse alternative pour les personnes qui utilisent Jasmine 2.0 autonome. Je crois que cela peut aussi fonctionner pour Jasmine 1.3, mais la syntaxe asynchrone est différente et plutôt moche.
Voici mon fichier SpecRunner.html modifié:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Jasmine Spec Runner v2.0.0</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css">
<!--
Notice that I just load Jasmine normally
-->
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script>
<!--
Here we load require.js but we do not use data-main. Instead we will load the
the specs separately. In short we need to load the spec files synchronously for this
to work.
-->
<script type="text/javascript" src="js/vendor/require.min.js"></script>
<!--
I put my require js config inline for simplicity
-->
<script type="text/javascript">
require.config({
baseUrl: 'js',
shim: {
'underscore': {
exports: '_'
},
'react': {
exports: 'React'
}
},
paths: {
jquery: 'vendor/jquery.min',
underscore: 'vendor/underscore.min',
react: 'vendor/react.min'
}
});
</script>
<!--
I put my spec files here
-->
<script type="text/javascript" src="spec/a-spec.js"></script>
<script type="text/javascript" src="spec/some-other-spec.js"></script>
</head>
<body>
</body>
</html>
Voici maintenant un exemple de fichier de spécifications:
describe("Circular List Operation", function() {
// The CircularList object needs to be loaded by RequireJs
// before we can use it.
var CircularList;
// require.js loads scripts asynchronously, so we can use
// Jasmine 2.0's async support. Basically it entails calling
// the done function once require js finishes loading our asset.
//
// Here I put the require in the beforeEach function to make sure the
// Circular list object is loaded each time.
beforeEach(function(done) {
require(['lib/util'], function(util) {
CircularList = util.CircularList;
done();
});
});
it("should know if list is empty", function() {
var list = new CircularList();
expect(list.isEmpty()).toBe(true);
});
// We can also use the async feature on the it function
// to require assets for a specific test.
it("should know if list is not empty", function(done) {
require(['lib/entity'], function(entity) {
var list = new CircularList([new entity.Cat()]);
expect(list.isEmpty()).toBe(false);
done();
});
});
});
Voici un lien vers la section de support asynchrone des documents Jasmine 2.0: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
Une autre option pour Jasmine 2.0 autonome consiste à créer un fichier boot.js et à le configurer pour exécuter vos tests une fois que tous vos modules AMD ont été chargés.
Le cas idéal de l'utilisateur final pour écrire des tests dans notre cas était de ne pas avoir à répertorier tous nos fichiers de spécifications ou dépendances dans une seule liste explicite, et de ne déclarer que vos fichiers de spécifications * comme des modules AMD avec des dépendances.
Exemple de spécification idéale: spec/javascript/sampleController_spec.js
require(['app/controllers/SampleController'], function(SampleController) {
describe('SampleController', function() {
it('should construct an instance of a SampleController', function() {
expect(new SampleController() instanceof SampleController).toBeTruthy();
});
});
});
Idéalement, le comportement en arrière-plan du chargement de la dépendance et de l'exécution des spécifications serait totalement opaque pour toute personne souhaitant écrire des tests dans le projet, et ils n'auront pas besoin de faire autre chose que de créer un fichier * spec.js avec les dépendances AMD .
Pour que tout cela fonctionne, nous avons créé un fichier de démarrage et configuré Jasmine pour l'utiliser ( http://jasmine.github.io/2.0/boot.html ), et ajouté de la magie pour boucler pour retarder temporairement l'exécution des tests jusqu'à ce que nos dépôts soient chargés:
Notre boot.js section "Exécution":
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
var currentWindowOnload = window.onload;
// Stack of AMD spec definitions
var specDefinitions = [];
// Store a ref to the current require function
window.oldRequire = require;
// Shim in our Jasmine spec require helper, which will queue up all of the definitions to be loaded in later.
require = function(deps, specCallback){
//Push any module defined using require([deps], callback) onto the specDefinitions stack.
specDefinitions.Push({ 'deps' : deps, 'specCallback' : specCallback });
};
//
window.onload = function() {
// Restore original require functionality
window.require = oldRequire;
// Keep a ref to Jasmine context for when we execute later
var context = this,
requireCalls = 0, // counter of (successful) require callbacks
specCount = specDefinitions.length; // # of AMD specs we're expecting to load
// func to execute the AMD callbacks for our test specs once requireJS has finished loading our deps
function execSpecDefinitions() {
//exec the callback of our AMD defined test spec, passing in the returned modules.
this.specCallback.apply(context, arguments);
requireCalls++; // inc our counter for successful AMD callbacks.
if(requireCalls === specCount){
//do the normal Jamsine HTML reporter initialization
htmlReporter.initialize.call(context);
//execute our Jasmine Env, now that all of our dependencies are loaded and our specs are defined.
env.execute.call(context);
}
}
var specDefinition;
// iterate through all of our AMD specs and call require with our spec execution callback
for (var i = specDefinitions.length - 1; i >= 0; i--) {
require(specDefinitions[i].deps, execSpecDefinitions.bind(specDefinitions[i]));
}
//keep original onload in case we set one in the HTML
if (currentWindowOnload) {
currentWindowOnload();
}
};
Nous conservons essentiellement nos spécifications de syntaxe AMD dans une pile, les supprimons, avons besoin des modules, exécutons le rappel avec nos assertions, puis exécutons Jasmine une fois que tout est terminé.
Cette configuration nous permet d'attendre que tous les modules AMD requis par nos tests individuels soient chargés, et ne casse pas les modèles AMD en créant des globaux. Il y a un peu de piratage dans le fait que nous remplaçons temporairement require, et ne chargeons notre code d'application qu'en utilisant require (our `src_dir:
in jasmine.yml est vide), mais l'objectif global ici est de réduire les frais généraux liés à l'écriture d'une spécification.
vous pouvez utiliser done
en combinaison avec des filtres avant pour tester les rappels asynchrones:
beforeEach(function(done) {
return require(['dist/sem-campaign'], function(campaign) {
module = campaign;
return done();
});
});
C'est ainsi que je fais pour exécuter une spécification de jasmin dans un html en utilisant AMD/requirejs pour toutes mes sources et spécifications.
Voici mon fichier index.html qui charge le jasmin puis mon 'starter test unit':
<html><head><title>unit test</title><head>
<link rel="shortcut icon" type="image/png" href="/jasmine/lib/jasmine-2.1.3/jasmine_favicon.png">
<link rel="stylesheet" href="/jasmine/lib/jasmine-2.1.3/jasmine.css">
<script src="/jasmine/lib/jasmine-2.1.3/jasmine.js"></script>
<script src="/jasmine/lib/jasmine-2.1.3/jasmine-html.js"></script>
<script src="/jasmine/lib/jasmine-2.1.3/boot.js"></script>
</head><body>
<script data-main="javascript/UnitTestStarter.js" src="javascript/require.js"></script>
</body></html>
puis mon UnitTestStarter.js est quelque chose comme ceci:
require.config({
"paths": {
....
});
require(['MySpec.js'], function()
{
jasmine.getEnv().execute();
})