Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How use require inside a spec - grunt + karma + jasmine in node

Vague version question:

  • How can I use require inside a spec with grunt?

Context:

I'm working on an existent node project that has no tests yet, so I read some and realized to use karma and jasmine.

I read some tutos (including these):

  • http://kwilson.me.uk/blog/use-karma-and-grunt-to-run-your-jasmine-tests-in-real-time/
  • http://ahexamples.blogspot.com.br/2014/03/example-of-jasmine-karma-sonar-grunt.html

So I'm trying to run my specs with grunt and getting this error:

X encountered a declaration exception ReferenceError: Can't find variable: require in file:///(...)-spec.js (line 2) (1)

The line is something like:

var myHelper = require(...);

But if I use via terminal "node-jasmine test" it works like a charm...

My project structure:

  • controllers/
  • helpers/
  • models/
  • node_modules/
  • resources/
  • test/
  • test/spec/
  • views/
  • app.js
  • Gruntfile.js
  • package.json

In my spec (inside test/spec/) I use a require('../../helpers/helper.js') and that's ok for node-jasmine, but no with grunt.

node-jasmine test:

.....

Finished in 0.015 seconds 5 tests, 5 assertions, 0 failures, 0 skipped

grunt:

Running "jasmine:pivotal" (jasmine) task Testing jasmine specs via PhantomJS

ReferenceError: Can't find variable: require at app.js:1 Service Helper Tests X encountered a declaration exception ReferenceError: Can't find variable: require in file:///(...)/test/spec/serviceHelper-spec.js (line 2) (1)

1 spec in 0.005s.

1 failures Warning: Task "jasmine:pivotal" failed. Use --force to continue.

Aborted due to warnings.

I have all packages installed into node_modules (nothing in dependencies in package.json) and my Gruntfile.js is:

'use strict';

module.exports = function(grunt) {
    var $srcFiles = 'app.js';
    var $testFiles = 'test/spec/*-spec.js';
    var $outputDir = 'test/target'
    var $junitResults = $outputDir + '/junit-test-results.xml';
    var $jasmineSpecRunner = $outputDir + '/_SpecRunner.html';
    var $coverageOutputDir = $outputDir + '/coverage';


    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Jasmine test
        jasmine: {
            pivotal: {
                src: $srcFiles,
                options: {
                    specs: $testFiles,
                    outfile: $jasmineSpecRunner,
                    keepRunner: 'true'  // keep SpecRunner/outfile file
                }
            }
        },

        // coverage using Karma
        karma: {
            continuous: {
                singleRun: 'true',
                browsers: [ 'PhantomJS' ]
            },

            options: {
                plugins: [
                    'karma-jasmine',
                    'karma-phantomjs-launcher',
                    'karma-junit-reporter',
                    'karma-coverage'
                ],
                frameworks: [ 'jasmine' ],
                files: [ $srcFiles, $testFiles ],
                reporters: [ 'junit', 'coverage' ],
                junitReporter: {
                  outputFile: $junitResults
                },
                preprocessors: {
                    // source files must be a literal string
                    'helpers/*.js': [ 'coverage' ]
                },
                coverageReporter: {
                    type: 'lcov',
                    dir: $coverageOutputDir
                }
            }
        },

        // export Karma coverage to SonarQube
        karma_sonar: {
            your_target: {
                // properties for SonarQube dashboard
                project: {
                    key: 'net.ahexample:ahexample-jasmine-karma-sonar',
                    name: 'Jasmine with Karma and SonarQube Example',
                    version: '0.0.1'
                }

                // sources property is set at runtime (see below)
            }
        },

        clean: [ $outputDir ]
    });


    /*
     * Task to set karma_sonar's sources property.
     * This is needed because karma (coverage) stores its results in a
     * directory whose name uses the browser's user agent info
     * (name/version and the platform name).
     * The latter may well he different to the OS name and so its needs an
     * OS to platform translator.
     * For example, OS name for Apple Mac OS X is Darwin.
     */
    grunt.registerTask('set-karma-sonar-sources-property', function() {
        var $done = this.async();
        var $phantomjs = require('karma-phantomjs-launcher/node_modules/phantomjs');
        var $spawn = require('child_process').spawn;
        var $phantomUserAgent = $spawn($phantomjs.path,
            // phantomjs script to print user agent string
            [ 'lib/phantomjs-useragent.js' ]
        );

        /*
         * Construct coverage LCOV file path from PhantomJS'
         * user agent string, then use it to set karma_sonar's
         * sources property.
         */
        $phantomUserAgent.stdout.on('data', function(msg) {
            var $useragent = require('karma/node_modules/useragent');
            var $agent = $useragent.parse(msg);
            // An example of dirName is 'PhantomJS 1.9.7 (Mac OS X)'
            var $dirName = $agent.toAgent() + ' (' + $agent.os + ')';
            var $coverageResults = $coverageOutputDir + '/' + $dirName + '/lcov.info';
            var $sonarSources = makeSonarSourceDirs($srcFiles, $coverageResults);
            var $karmaSonarConfig = 'karma_sonar';
            var $ksConfig = grunt.config($karmaSonarConfig);

            grunt.log.writeln('coverage LCOV file: ' + $coverageResults);
            $ksConfig['your_target']['sources'] = $sonarSources;
            grunt.config($karmaSonarConfig, $ksConfig);

        });

        $phantomUserAgent.on('close', function(exitCode) {
            $done();
        });


        /*
         * Create sonar source object for each directory of source file pattern.
         */
        function makeSonarSourceDirs($filesPattern, $coverageResults) {
            var $path = require('path');
            var $dirs = [];

            grunt.file.expand(
                {
                    filter: function($filePath) {
                        $dirs.push({
                            path: $path.dirname($filePath),
                            prefix: '.',    // path prefix in lcov.info
                            coverageReport: $coverageResults,
                            testReport: $junitResults
                        });
                    }
                },
                $filesPattern
            );

            return $dirs;
        }
    });


    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-karma');
    grunt.loadNpmTasks('grunt-karma-sonar');


    grunt.registerTask('test', [ 'jasmine', 'karma:continuous' ]);
    grunt.registerTask('sonar-only', [ 'set-karma-sonar-sources-property', 'karma_sonar' ]);
    grunt.registerTask('sonar', [ 'test', 'sonar-only' ]);
    grunt.registerTask('default', 'test');
}

Thank you for your attention.

like image 440
FC.Araujo Avatar asked Aug 20 '14 19:08

FC.Araujo


1 Answers

How

It depends:

  1. If you have some application code that you need to test against a browser (e.g. Angular, Backbone, etc.) - Use Karma and don't use require. Then just make sure your helpers.js file loads before the tests themselves.

    // @file Gruntfile.js
    // https://github.com/karma-runner/grunt-karma
    grunt.initConfig({        
      karma: {
        client: {
          options: {
            files: ['client/*.js', 'helpers/*.js', 'test/*.js']
          }
        }
      }
    });
    
    // @file helpers.js
    (function () {
      window.helpers = {
        foo: function () {
          return 'bar';
        }
      };
    })();
    
    // @file spec.js
    (function (helpers) {
    
      it('does the thing', function () {
        expect(helpers.foo()).toBe('bar');
      });
    
    })(window.helpers);
    
  2. If you don't need to run your tests against a browser (i.e. you are strictly testing NodeJS code), you can simplify your setup by removing Karma and strictly using Jasmine:

    // @file Gruntfile.js
    // https://github.com/gruntjs/grunt-contrib-jasmine
    grunt.initConfig({
      jasmine: {
        server: {
          src: 'server/*.js',
          options: {
            specs: 'test/*.js',
            helpers: 'helpers/*.js'
          }
        } 
      }
    });
    
    // @file helpers.js
    (function () {
      module.exports = {
        foo: function () {
          return 'bar';
        }
      };
    })();
    
    // @file spec.js
    (function () {
      var helpers = require('helpers'); // require is available
    
      it('does the thing', function () {
        expect(helpers.foo()).toBe('bar');
      });
    
    })();
    

Why

require doesn't exist because you are using Karma to run your tests. Karma simply loads files in a browser of your choosing and executes them in the order you provide in your karma.conf.js. It internally uses the test framework you provide (in this case Jasmine) to run tests against the browser(s) you provide (in this case PhantomJS).

As is true with all JavaScript, the variable context is defined by the closure it is contained within.

  • The Jasmine binary internally uses NodeJS, which emulates CommonJS require, making the require function available to you within the context of your node application.

  • The Karma runner does what is the equivalent of writing <script src="[path]"> tags to the browser, which then each load the corresponding file into PhantomJs. As a result, your javascript context is global, and your files only have access to the global context. In a browser, the global context is defined by everything attached to the window object, and window.require does not inherently exist.

like image 130
Justin Helmer Avatar answered Sep 29 '22 06:09

Justin Helmer