Subsecciones

Putting Things Together

Unit Testing: Mocha

Introducción

Mocha is a feature-rich JavaScript test framework running on node.js and the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

mocha init

[~/srcPLgrado/mocha-chai-browser-demo(master)]$ mocha --help

  Usage: _mocha [debug] [options] [files]

  Commands:

    init <path>            initialize a client-side mocha setup at <path>

  Options:

    -h, --help                      output usage information
    -V, --version                   output the version number
    -r, --require <name>            require the given module
    -R, --reporter <name>           specify the reporter to use
    -u, --ui <name>                 specify user-interface (bdd|tdd|exports)
    -g, --grep <pattern>            only run tests matching <pattern>
    -i, --invert                    inverts --grep matches
    -t, --timeout <ms>              set test-case timeout in milliseconds [2000]
    -s, --slow <ms>                 "slow" test threshold in milliseconds [75]
    -w, --watch                     watch files for changes
    -c, --colors                    force enabling of colors
    -C, --no-colors                 force disabling of colors
    -G, --growl                     enable growl notification support
    -d, --debug                     enable node's debugger, synonym for node --debug
    -b, --bail                      bail after first test failure
    -A, --async-only                force all tests to take a callback (async)
    -S, --sort                      sort test files
    --recursive                     include sub directories
    --debug-brk                     enable node's debugger breaking on the first line
    --globals <names>               allow the given comma-delimited global [names]
    --check-leaks                   check for global variable leaks
    --interfaces                    display available interfaces
    --reporters                     display available reporters
    --compilers <ext>:<module>,...  use the given module(s) to compile files
    --inline-diffs                  display actual/expected differences inline within each string
    --no-exit                       require a clean shutdown of the event loop: mocha will not call process.exit

[~/srcPLgrado]$ mocha init chuchu
[~/srcPLgrado]$ ls -ltr
total 16
....
drwxr-xr-x   6 casiano  staff  204 20 ene 11:16 chuchu
[~/srcPLgrado]$ tree chuchu/
chuchu/
|-- index.html
|-- mocha.css
|-- mocha.js
`-- tests.js

[~/srcPLgrado/mocha-tutorial]$ cat test/test.js 
var assert = require("assert")
describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
      assert.equal( 0, [1,2,3].indexOf(99));
    })
  })
})

[~/srcPLgrado/mocha-tutorial]$ mocha
  .
  0 passing (5ms)
  1 failing

  1) Array #indexOf() should return -1 when the value is not present:
     AssertionError: 0 == -1
      at Context.<anonymous> (/Users/casiano/local/src/javascript/PLgrado/mocha-tutorial/test/test.js:7:14)

Mocha allows you to use any assertion library you want, if it throws an error, it will work! This means you can utilize libraries such as should.js, node's regular assert module, or others.

Browser support

Mocha runs in the browser.

A typical setup might look something like the following, where we call mocha.setup('bdd') to use the BDD interface before loading the test scripts, running them onload with mocha.run().

<html>
<head>
  <meta charset="utf-8">
  <title>Mocha Tests</title>
  <link rel="stylesheet" href="mocha.css" />
</head>
<body>
  <div id="mocha"></div>
  <script src="jquery.js"></script>
  <script src="expect.js"></script>
  <script src="mocha.js"></script>

  <script>mocha.setup('bdd')</script>

  <script src="test.array.js"></script>
  <script src="test.object.js"></script>
  <script src="test.xhr.js"></script>

  <script>
    mocha.checkLeaks();
    mocha.globals(['jQuery']);
    mocha.run();
  </script>

</body>
</html>

TDD

The Mocha TDD interface provides suite(), test(), setup(), and teardown().

suite('Array', function(){
  setup(function(){
    // ...
  });

  suite('#indexOf()', function(){
    test('should return -1 when not present', function(){
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

Véase


Karma

[~/srcPLgrado/mocha-chai-browser-demo(master)]$ karma --help
Karma - Spectacular Test Runner for JavaScript.

Usage:
  /usr/local/bin/karma <command>

Commands:
  start [<configFile>] [<options>] Start the server / do single run.
  init [<configFile>] Initialize a config file.
  run [<options>] [ -- <clientArgs>] Trigger a test run.
  completion Shell completion for karma.

Run --help with particular command to see its description and available options.

Options:
  --help     Print usage and options.
  --version  Print current version.

In order to serve us well, Karma needs to know about our project in order to test it and this is done via a configuration file.

The configuration file can be generated using karma init:

$ karma init my.conf.js

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

http://requirejs.org/

Do you want to capture a browser automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> 

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.


Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

Config file generated at "/Users/casiano/local/src/javascript/PLgrado/mocha-tutorial/karma.conf.js".

The configuration file can be written in CoffeeScript as well. In fact, if you execute karma init with a .coffee filename extension, it will generate a CoffeeScript file.

Of course, you can write the config file by hand or copy paste it from another project ;-)

[~/srcPLgrado/mocha-tutorial]$ cat karma.conf.js 
// Karma configuration
// Generated on Mon Jan 20 2014 16:21:22 GMT+0000 (WET)

module.exports = function(config) {
  config.set({

    // base path, that will be used to resolve files and exclude
    basePath: '',


    // frameworks to use
    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser
    files: [
      
    ],


    // list of files to exclude
    exclude: [
      
    ],


    // test results reporter to use
    // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera (has to be installed with `npm install karma-opera-launcher`)
    // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
    // - PhantomJS
    // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
    browsers: ['Chrome', 'Firefox'],


    // If browser does not capture in given timeout [ms], kill it
    captureTimeout: 60000,


    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};
When starting Karma, the configuration file path can be passed in as the first argument. By default, Karma will look for karma.conf.js in the current directory.
# Start Karma using your configuration
$ karma start my.conf.js

Some configurations, which are already present within the configuration file, can be overridden by specifying the configuration as a command line argument for when Karma is executed.

karma start karma-conf.js --command-one --command-two

[~/srcPLgrado/mocha-tutorial]$ karma start --help
Karma - Spectacular Test Runner for JavaScript.

START - Start the server / do a single run.

Usage:
  /usr/local/bin/karma start [<configFile>] [<options>]

Options:
  --port                <integer> Port where the server is running.                            
  --auto-watch          Auto watch source files and run on change.                             
  --no-auto-watch       Do not watch source files.                                             
  --log-level           <disable | error | warn | info | debug> Level of logging.              
  --colors              Use colors when reporting and printing logs.                           
  --no-colors           Do not use colors when reporting or printing logs.                     
  --reporters           List of reporters (available: dots, progress, junit, growl, coverage). 
  --browsers            List of browsers to start (eg. --browsers Chrome,ChromeCanary,Firefox).
  --capture-timeout     <integer> Kill browser if does not capture in given time [ms].         
  --single-run          Run the test when browsers captured and exit.                          
  --no-single-run       Disable single-run.                                                    
  --report-slower-than  <integer> Report tests that are slower than given time [ms].           
  --help                Print usage and options.

Using Karma with Mocha

To use Karma with Mocha we need the karma-mocha adapter.

If we want to pass configuration options directly to mocha you can do this in the following way

// karma.conf.js
module.exports = function(config) {
  config.set({
    frameworks: ['mocha'],

    files: [
      '*.js'
    ],

    client: {
      mocha: {
        ui: 'tdd'
      }
    }
  });
};
(By default the ui is bdd).

Here is an example (https://github.com/crguezl/nathanuniversityexercisesPL/blob/master/scheem8/karma.conf.js):

[~/srcPLgrado/nathansuniversity/exercises/scheem8(master)]$ cat karma.conf.js 
// Karma configuration
// Generated on Tue Jan 21 2014 12:20:45 GMT+0000 (WET)

module.exports = function(config) {

  config.set({

    // base path, that will be used to resolve files and exclude
    basePath: '',


    // frameworks to use
    frameworks: ['mocha'],


    // list of files / patterns to load in the browser
    files: [
      'js/chai.js',
      'js/jquery-1.10.2.js',
      'js/mocha.js',
      'js/scheem8.js',
      'js/simpletest.js'
    ],


    // list of files to exclude
    exclude: [
      
    ],


    // test results reporter to use
    // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera (has to be installed with `npm install karma-opera-launcher`)
    // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
    // - PhantomJS
    // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
    browsers: ['Chrome', 'Firefox'],


    // If browser does not capture in given timeout [ms], kill it
    captureTimeout: 60000,


    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false,

    client: {
      mocha: {
        ui: 'tdd'
      }
    }
  });
};

Load HTML files with Karma

If you have one html file:

[~/srcPLgrado/karma/html]$ cat template.html 
<div id="tpl">content of the template</div>
which you want to load and then get all elements from that html page in your test script, you can use the html2js preprocessor, which basically converts HTML files into JavaScript strings and include these files.
[~/srcPLgrado/karma/html]$ cat karma.conf.js 
module.exports = function(karma) {
  karma.configure({
    basePath: '',
    frameworks: ['jasmine'],
    files: [ '*.js', '*.html' ], 
    preprocessors: { '*.html': 'html2js' },
    ....
Then, you can access these strings in your test:
[~/srcPLgrado/karma/html]$ cat test.js 
describe('template', function() {
  it('should expose the templates to __html__', function() {
    document.body.innerHTML = __html__['template.html'];
    expect(document.getElementById('tpl')).toBeDefined();
  })
})
See

Grunt

http://gruntjs.com/getting-started

npm install -g grunt-cli

A typical setup will involve adding two files to your project: package.json and the Gruntfile.

package.json

Gruntfile

The Gruntfile.js or Gruntfile.coffee file is a valid JavaScript or CoffeeScript file that belongs in the root directory of your project, next to the package.json file, and should be committed with your project source.

A Gruntfile is comprised of the following parts:

An example Gruntfile

In the following Gruntfile, project metadata is imported into the Grunt config from the project's package.json file and the

grunt-contrib-uglify

plugin's uglify task is configured to minify a source file and generate a banner comment dynamically using that metadata.

When grunt is run on the command line, the uglify task will be run by default.

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/<%= pkg.name %>.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    }
  });

  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Default task(s).
  grunt.registerTask('default', ['uglify']);

};
Now that you've seen the whole Gruntfile, let's look at its component parts.

The "wrapper" function

Every Gruntfile (and gruntplugin) uses this basic format, and all of your Grunt code must be specified inside this function:

module.exports = function(grunt) {
  // Do grunt-related things in here
};

Project and task configuration

Most Grunt tasks rely on configuration data defined in an object passed to the grunt.initConfig method.

In this example, grunt.file.readJSON('package.json') imports the JSON metadata stored in package.json into the grunt config. Because <% %> template strings may reference any config properties, configuration data like filepaths and file lists may be specified this way to reduce repetition.

You may store any arbitrary data inside of the configuration object, and as long as it doesn't conflict with properties your tasks require, it will be otherwise ignored. Also, because this is JavaScript, you're not limited to JSON; you may use any valid JS here. You can even programmatically generate the configuration if necessary.

Like most tasks, the grunt-contrib-uglify plugin's uglify task expects its configuration to be specified in a property of the same name. Here, the banner option is specified, along with a single uglify target named build that minifies a single source file to a single destination file.

// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    build: {
      src: 'src/<%= pkg.name %>.js',
      dest: 'build/<%= pkg.name %>.min.js'
    }
  }
});

A simple Grunt.js example

https://github.com/UWMadisonUcomm/grunt-simple-example

[~/srcPLgrado/grunt-simple-example(master)]$ pwd
/Users/casiano/srcPLgrado/grunt-simple-example
[~/srcPLgrado/grunt-simple-example(master)]$ git remote -v
origin  git@github.com:UWMadisonUcomm/grunt-simple-example.git (fetch)
origin  git@github.com:UWMadisonUcomm/grunt-simple-example.git (push)
[~/srcPLgrado/grunt-simple-example(master)]$ ls
Gruntfile.js Readme.md    assets       index.html   node_modules package.json src

[~/srcPLgrado/grunt-simple-example(master)]$ cat Gruntfile.js 
module.exports = function(grunt){
  grunt.initConfig({
    uglify: {
      main: {
        files: {
          'assets/app.min.js': [
            'src/javascripts/jquery-1.10.2.min.js',
            'src/javascripts/bootstrap.js',
            'src/javascripts/application.js'
          ]
        }
      }
    },
    less: {
      application: {
        options: {
          yuicompress: true
        },
        files: {
          "assets/app.min.css": "src/stylesheets/application.less"
        }
      }
    },
    watch: {
      javascripts: {
        files: ['src/javascripts/**/*'],
        tasks: ['uglify']
      },
      stylesheets: {
        files: ['src/stylesheets/**/*'],
        tasks: ['less']
      }
    }
  });

  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Register tasks
  grunt.registerTask('default', ['uglify', 'less']);
}

[~/srcPLgrado/grunt-simple-example(master)]$ cat package.json 
{
  "name": "grunt-simple-example",
  "version": "0.0.1",
  "main": "index.js",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-cssmin": "~0.6.2",
    "grunt-contrib-less": "~0.7.0",
    "grunt-contrib-uglify": "~0.2.4",
    "grunt-contrib-watch": "~0.5.3"
  },
  "author": "Bryan Shelton",
  "license": "BSD-2-Clause"
}

[~/srcPLgrado/grunt-simple-example(master)]$ npm install
npm WARN package.json grunt-simple-example@0.0.1 No repository field.
[~/srcPLgrado/grunt-simple-example(master)]$

[~/srcPLgrado/grunt-simple-example(master)]$ grunt watch
Running "watch" task
Waiting...OK
>> File "src/javascripts/application.js" changed.

Running "uglify:main" (uglify) task
File "assets/app.min.js" created.

Done, without errors.
Completed in 3.897s at Mon Jan 20 2014 19:02:03 GMT+0000 (WET) - Waiting...


GitHub Project Pages

Project Pages are kept in the same repository as the project they are for.

These pages are similar to User and Org Pages, with a few slight differences:

  1. Setting up Pages on a project requires a new "orphan" branch in your repository. The safest way to do this is to start with a fresh clone.

    git clone https://github.com/user/repository.git
    # Clone our repository
    # Cloning into 'repository'...
    remote: Counting objects: 2791, done.
    remote: Compressing objects: 100% (1225/1225), done.
    remote: Total 2791 (delta 1722), reused 2513 (delta 1493)
    Receiving objects: 100% (2791/2791), 3.77 MiB | 969 KiB/s, done.
    Resolving deltas: 100% (1722/1722), done.
    
  2. Now that we have a clean repository, we need to create the new branch and remove all content from the working directory and index.

    cd repository
    
    git checkout --orphan gh-pages
    # Creates our branch, without any parents (it's an orphan!)
    # Switched to a new branch 'gh-pages'
    
    git rm -rf .
    # Remove all files from the old working tree
    # rm '.gitignore'
    
  3. Now we have an empty working directory. We can create some content in this branch and push it to GitHub. For example:
    echo "My GitHub Page" > index.html
    git add index.html
    git commit -a -m "First pages commit"
    git push origin gh-pages
    

Casiano Rodríguez León
2015-01-25