让 requirejs 与 Jasmine 一起工作

Posted

技术标签:

【中文标题】让 requirejs 与 Jasmine 一起工作【英文标题】:Getting requirejs to work with Jasmine 【发布时间】:2013-05-01 15:35:03 【问题描述】:

我首先想说的是,我是 RequireJS 的新手,甚至是 Jasmine 的新手。

我在使用 SpecRunner 时遇到了一些问题,需要 JS。我一直在关注 Uzi Kilon 和 Ben Nadel(以及其他一些人)的教程,他们帮助了一些人,但我仍然遇到一些问题。

似乎,如果在测试中出现错误(我可以特别想到一个,类型错误),规范运行器 html 将显示。这告诉我我在 javascript 中有一些问题。但是,在我修复了这些错误之后,不再显示 HTML。 我根本无法显示测试运行器。有人能发现我的代码有什么问题会导致这个问题吗?

这是我的目录结构

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

这里是 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>

这里是 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'
        
    
);

这是一个规范:

require(["../lib/jasmine/src/notepad"], function (notepad) 
    describe("returns titles", function() 
        expect(notepad.noteTitles()).toEqual("");


    );
);

记事本来源:

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;
        
    ;
);

以及注释来源(JIC):

define(function ()
    var note = function(title, content) 
        this.title = title;
        this.content = content;
    ;

    return note;
);

我已确保就应用而言,路径是正确的。一旦我完成这项工作,我就可以配置这些路径,这样它就不会那么糟糕了。

【问题讨论】:

你能试试这个吗?在要求之外定义 HtmlReported。调用只在里面执行。 var jasmineEnv = jasmine.getEnv(); jasmineEnv.addReporter(new jasmine.HtmlReporter());要求(['suites/aSpec.js'], function(spec) jasmineEnv.execute(); ); 对于 Jasmine 2.0.0 独立版,这个答案对我有用:***.com/questions/19240302/… 【参考方案1】:

这就是我如何使用 AMD/requirejs 在我的所有源代码和规范中运行 html 中的茉莉花规范。

这是我的 index.html 文件,它加载了 jasmine,然后是我的“单元测试启动器”:

<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>

然后我的 UnitTestStarter.js 是这样的:

require.config(
    "paths": 
        ....
);
require(['MySpec.js'], function()

    jasmine.getEnv().execute();
)

【讨论】:

【参考方案2】:

我设法通过一些试验和错误来解决这个问题。主要问题是,当您编写规范时,它不是您要创建的要求,而是要使用定义:

原文:

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


    );
);

工作:

define(["lib/jasmine/src/notepad"], function (notepad) 
    describe("returns titles", function () 

        it("something", function() 

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

    );
);

在做了一些研究之后,很明显,当使用 RequireJS 时,任何你想要 require() 使用的东西都必须包含在一个定义中(我猜现在看起来很明显)。您可以看到,在 specRunner.js 文件中,执行测试时使用了 require(因此需要“定义”规范。

另一个问题是,在创建规范时,describe() 和 it() 是必需的(不仅仅是我在发布的示例中的描述)。

原文:

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


    );

工作:

describe("returns titles", function () 

        it("something", function() 

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

    );

我也改变了测试运行器所在的位置,但这是一个重构,并没有改变测试的结果。

再次,这里是文件和更改:

note.js:保持不变

notepad.js:保持不变

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");
        );

    );
);

【讨论】:

+1 for 主要问题是,当您编写规范时,它不是您想要创建的要求,而是要使用定义。如果您使用 require,您的测试有时会工作,有时会出现错误 no specs found【参考方案3】:

您可以将 done 与 before 过滤器结合使用来测试异步回调:

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

【讨论】:

【参考方案4】:

Jasmine 2.0 独立版的另一个选项是创建一个 boot.js 文件并设置它以在加载所有 AMD 模块后运行您的测试。

在我们的案例中编写测试的理想最终用户案例是不必在一个明确的列表中列出我们所有的规范文件或依赖项,并且只需要将您的 *spec 文件声明为具有依赖项的 AMD 模块。

理想规范示例: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();
      );
  );
);

理想情况下,加载依赖项和运行规范的后台行为对于想要编写测试的项目的任何人来说都是完全不透明的,他们不需要做任何事情,只需要创建一个 *spec.js 文件具有 AMD 依赖项。

为了让这一切正常运行,我们创建了一个引导文件并配置 Jasmine 以使用它 (http://jasmine.github.io/2.0/boot.html),并添加了一些魔法来环绕 require 以暂时延迟运行测试,直到我们加载了我们的 deps:

我们的 boot.js 的“执行”部分:

/**
 * ## 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();
  

;

我们基本上将我们的 AMD 语法规范保存在一个堆栈中,将它们弹出,请求模块,使用我们的断言执行回调,然后在所有内容加载完成后运行 Jasmine。

这种设置允许我们等待,直到我们的各个测试所需的所有 AMD 模块都加载完毕,并且不会通过创建全局变量来破坏 AMD 模式。我们临时覆盖 require 并且只使用 require 加载我们的应用程序代码(我们在 jasmine.yml 中的`src_dir: 是空的),但这里的总体目标是减少编写规范的开销。

【讨论】:

【参考方案5】:

只需将此添加为您使用 Jasmine 2.0 独立版的人的替代答案。我相信这也适用于 Jasmine 1.3,但异步语法不同而且有点难看。

这是我修改后的 SpecRunner.html 文件:

<!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>

现在这里是一个示例规范文件:

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();
        );
    );
);

这是 Jasmine 2.0 文档中异步支持部分的链接:http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support

【讨论】:

值得注意的是,这是我发现的唯一一个与node-webkit 结合使用的解决方案,并与RequireJS r.js plugin 结合使用,这样我就可以测试导入 AMD 和 Node.js 模块的代码。 您的规格没有使用 amd,我认为这是这个问题的全部目的。

以上是关于让 requirejs 与 Jasmine 一起工作的主要内容,如果未能解决你的问题,请参考以下文章

Karma + Jasmine + Durandal + KnockoutJS + RequireJS 单元测试中不匹配的匿名 define() 模块

将 JSLint/Hint 与 requirejs 一起使用

如何在 zepto 中使用 requirejs

将 Jquery.hammer 和 Hammer 2.0.2 与 RequireJS 一起使用

使用 Jasmine 进行异步测试

可以将 webpack 4 模块配置为允许 Jasmine 监视其成员吗?