67 votes

Faire fonctionner requirejs avec Jasmine

Je veux d'abord dire que je suis nouveau à RequireJS et encore plus nouveau à Jasmine.

J'ai quelques problèmes avec le SpecRunner et require JS. J'ai suivi les tutoriels d'Uzi Kilon et de Ben Nadel (ainsi que d'autres) et ils m'ont aidé, mais j'ai encore des problèmes.

Il semble que, si une erreur est lancée dans le test (je pense à une en particulier, une erreur de type), le spec runner html s'affiche. Cela me dit que j'ai des problèmes dans le javascript. Cependant, après avoir corrigé ces erreurs, aucun HTML ne s'affiche plus. Je ne parviens pas à afficher le programme d'exécution des tests. Quelqu'un peut-il trouver une erreur dans mon code qui causerait ce problème ?

Voici mon structure des 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é qu'en ce qui concerne l'application, les chemins sont corrects. Une fois que j'aurai réussi à faire fonctionner l'application, je pourrai m'amuser à configurer les chemins d'accès de manière à ce qu'ils ne soient pas si dégoûtants.

58voto

The Sheek Geek Points 1423

J'ai réussi à le faire fonctionner avec quelques essais et erreurs. Le problème principal était que lorsque vous écrivez des spécifications, ce n'est pas un require que vous voulez créer, vous voulez utiliser define :

Original :

require(["/lib/jasmine/src/notepad"], function (notepad) {
    describe("returns titles", function() {
        expect(notepad.noteTitles()).toEqual("pick up the kids get milk");

    });
});

Travailler :

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, lors de l'utilisation de RequireJS, tout ce que vous voulez que le require() utilise doit être enveloppé dans un define (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 (donc le besoin de "définir" les specs.

L'autre problème est que, lors de la création de spécifications, le describe() ET le it() sont nécessaires (pas seulement le describe comme dans l'exemple affiché).

Original :

describe("returns titles", function() {
        expect(notepad.noteTitles()).toEqual("pick up the kids get milk");

    });

Travailler :

describe("returns titles", function () {

        it("something", function() {

            expect(notepad.noteTitles()).toEqual("pick up the kids get milk ");
        });

    });

J'ai également changé l'emplacement de l'exécuteur de tests, mais il s'agissait d'une refonte qui n'a pas modifié 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");
        });

    });
});

12voto

Gohn67 Points 5022

J'ajoute simplement ceci comme réponse alternative pour les personnes qui utilisent Jasmine 2.0 standalone. Je pense que cela peut également 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 spec :

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 sur le support asynchrone de la documentation de Jasmine 2.0 : http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support

3voto

TopherBullock Points 11

Une autre option pour Jasmine 2.0 standalone est de créer un fichier boot.js et de le configurer pour exécuter vos tests après que tous vos modules AMD aient été chargés.

Dans notre cas, l'utilisateur final idéal pour l'écriture des tests était de ne pas avoir à énumérer tous nos fichiers de spécification ou nos dépendances dans une liste explicite, et d'avoir seulement l'obligation de déclarer vos *fichiers de spécification 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 de fond consistant à charger la dépendance et à exécuter les spécifications serait totalement opaque pour toute personne arrivant sur le projet et souhaitant écrire des tests, et elle n'aurait rien d'autre à faire 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 nous avons ajouté un peu de magie pour contourner require et retarder temporairement l'exécution des tests jusqu'à ce que nous ayons chargé nos deps :

Notre site 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();
  }

};

En gros, nous conservons nos spécifications syntaxiques AMD dans une pile, nous les extrayons, nous exigeons les modules, nous exécutons le rappel contenant nos assertions, puis nous exécutons Jasmine une fois que tout a été chargé.

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 une petite astuce dans le fait que nous surchargeons temporairement require, et chargeons seulement le code de notre application en utilisant require (notre module `src_dir: sur jasmine.yml est vide), mais l'objectif global ici est de réduire la surcharge de l'écriture d'une spécification.

3voto

lfender6445 Points 1361

Vous pouvez utiliser done en combinaison avec les filtres before pour tester les callbacks asynchrones :

  beforeEach(function(done) {
    return require(['dist/sem-campaign'], function(campaign) {
      module = campaign;
      return done();
    });
  });

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X