electro-func

2.0.2 • Public • Published

A barebones functional testing framework for client-side javascript using a headless electron instance for rendering.

Quick Start

Install electro-func in your project by running the following from your project's directory:

$ npm install electro-func

Add the test harness and your tests to the bottom of any files you want to test:

#!html
<!-- index.html -->
<body>
    <!-- ... -->
    <script src="node_modules/electro-func/harness.js"></script>
    <script src="tests/example.test.js"></script>
</body>

Run your tests by running the following from your project's directory (this example runs any tests in index.html):

$ node_modules/.bin/electro-func index

JQuery

If your project uses jQuery, you will need to add the following extra code before the <script> that includes jQuery in your page, i.e:

#!html
<script>
    if (window.require) {
        window.$ = window.jQuery = require('./js/jquery-3.1.1.min');
    }
</script>
<script src="js/jquery-3.1.1.min.js"></script>

The reason for this is because the jQuery library tries to detect whether it's running in a node.js environment or running in a browser environment. Because we use electron to render the pages and not an actual browser, jQuery thinks it's running in a node.js environment and doesn't add itself to the global window scope which any client-side javascript will be depending on.

Examples

The examples/ directory contains an example of the most minimal test in the basic/ directory while the app/ directory contains an example of a full single-page application and its associated tests which you can use to get a better idea of how to use the framework.

You can view the examples in your browser or run them yourself. To do that, you need to install electro-func to the directory of the example you want to run with:

$ npm install

Then you can run the basic example with:

$ node_modules/.bin/electro-func

And you can run the app example with:

$ node_modules/.bin/electro-func index

Adding Tests to a Project

The following is a brief guide/tutorial that goes beyond the quick start guide and walks through adding some functional tests to an existing site's javascript.

All the code can be found in examples/ directory under the guide/ directory.

Project structure

We start with a website that might look like this:

├── css
│   └── site.css 
├── js
│   ├── jquery-3.1.1.min.js
│   └── site.js
├── img
│   ├── bg-texture.png
│   └── logo.png
├── about.html
├── example.html
└── index.html

On the site's index page, there is a form that allows the visitor to input their favourite colour and it will store this value for the next time they visit. Our index.html might look something like this:

#!html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Favourite Colour</title>
    <link rel="stylesheet" href="css/site.css">
</head>
<body>
    <div id="greeting"></div>
    <form id="colour-form">
        <label for="colour">Favourite colour</label>
        <input id="colour" type="text">
        <button id="save" type="button">Save</button>
    </form>
    
    <script src="js/jquery-3.1.1.min.js"></script>
    <script src="js/site.js"></script>
</body>
</html>

Code

The code that manages the loading and saving of visitors' favourite colours is in site.js:

#!js
'use strict';

const loadFavouriteColour = () => {
    const colour = localStorage['favourite-colour'];
    const msg =
        colour
            ? 'Your favourite colour is ' + colour
            : "You don't have a favourite colour yet";
    $('#greeting').text(msg);
};

const saveFavouriteColour = () => {
    const colour = $('#colour').val();
    if (colour.length < 1) {
        localStorage.removeItem('favourite-colour');
    } else {
        localStorage['favourite-colour'] = colour;
    }
};

$('#save').click(saveFavouriteColour);
loadFavouriteColour();

We want to write some tests to make sure this code behaves as expected.

Tests

We write our tests in tests/site.test.js:

#!js
'use strict';

// Create the test suite.
_tests.Site = {};

// Clean up any saved state before each test.
_tests.Site._before = done => {
    Object.keys(localStorage).forEach(key => localStorage.removeItem(key));
    done();
};

_tests.Site.testLoadNoFavouriteColour = done => {
    loadFavouriteColour();
    assert.equal($('#greeting').text(), "You don't have a favourite colour yet");
    done();
};

_tests.Site.testLoadFavouriteColour = done => {
    localStorage['favourite-colour'] = 'black';
    loadFavouriteColour();
    assert.equal($('#greeting').text(), 'Your favourite colour is black');
    done();
};

_tests.Site.testSaveFavouriteColour = done => {
    const click = $.Event('click');
    $('#colour').val('black');
    $('#save').trigger(click);
    assert.equal(localStorage['favourite-colour'], 'black');
    done();
};

_tests.Site.testSaveBlankFavouriteColour = done => {
    const click = $.Event('click');
    $('#colour').val('');
    $('#save').trigger(click);
    expect(localStorage['favourite-colour']).to.not.exist;
    done();
};

Tests belong to suites and suites belong to the global _tests object which is provided by the electro-func framework.

To add a suite, simply create a new object on the global _tests object. For example, using normal property syntax:

#!js
_tests.TestSuite = {};

Or, if you need spaces, for example, you can use dictionary syntax:

#!js
_tests['A Suite of Tests'] = {};

Note: The names of tests or test suites cannot begin with an underscore ('_').

To add a test to a suite, simply add a function to your suite object with the name of the test, e.g.:

#!js
_tests.TestSuite.testSomeFunction = done => {
    done();
};

As with test suites, you can also use dictionary syntax:

#!js
_tests.TestSuite['testing something'] = done => {
    done();
};

All tests accept a single argument, done which is a function that must be called once the test is complete. This allows you to use callbacks and promises in your tests, e.g.:

#!js
_tests.TestSuite.testTimeout = done => {
    setTimeout(() => {
        assert(true);
        done();
    }, 200);
};

If you forget to call done, your test will timeout.

See the Advanced Usage section for information on _before, _after, _beforeSuite, and _afterSuite.

Putting It All Together

We now have a choice on how we want to proceed: We can add the test code directly to index.html where it won't affect the normal functioning of the site ()except to increase page load time); or we can create our own TestRunner.html where we set up some special environment for running our tests in.

The choice will depend on the structure of your project. For simplicity, we will just add our tests to the index page.

The first thing we need to do, is allow jQuery to run inside electron. We add the code specified in the JQuery section.

Next we need to add the electro-func harness and our test to the bottom of the index page, after all our other scripts. The index page now looks like this:

#!html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Favourite Colour</title>
    <link rel="stylesheet" href="css/site.css">
</head>
<body>
    <div id="greeting"></div>
    <form id="colour-form">
        <label for="colour">Favourite colour</label>
        <input id="colour" type="text">
        <button id="save" type="button">Save</button>
    </form>
    
    <script>
    	if (window.require) {
    		window.$ = window.jQuery = require('./js/jquery-3.1.1.min');
    	}
    </script>
    
    <script src="js/jquery-3.1.1.min.js"></script>
    <script src="js/site.js"></script>
    
    <!-- Tests -->
    <script src="node_modules/electro-func/harness.js"></script>
    <script src="tests/site.test.js"></script>
</body>
</html>

Running

In order to run the tests, we run the following command from our project's directory:

$ node_modules/.bin/electro-func index

And should see some similar output to the following:

  +------------------+
  |    INDEX.HTML    |
  +------------------+
  
  Site
    ✔ testLoadNoFavouriteColour (2ms)
    ✔ testLoadFavouriteColour (2ms)
    ✔ testSaveFavouriteColour (2ms)
    ✔ testSaveBlankFavouriteColour (2ms)
  
  4 passing  (8ms)

Advanced Usage

Aside from the bare minimum required to run functional tests, electro-func also includes some additional features.

Debug Mode

You can pass the --debug flag on the command line which turns off electron's headless mode. A browser window will spawn with the devtools panel open and you can use its debugging features to debug any test failures you might be having. You can rerun the tests from within the browser window by running location.reload() in the devtools console.

Timeout

By default, any test that takes longer than 2000ms to run will timeout and automatically fail. This is in case there are some complicated nested callbacks or promises being used such that done never gets called and the framework has no idea whether it can continue to the next test or not.

If some of your tests legitimately require longer than 2000ms to run, you can increase this timeout by specifying the --timeout n flag on the command line, where n is the number of milliseconds timeout you want.

Setup and Teardown

The framework provides some facility for providing setup and teardown functions to your tests. You usually want this to ensure that your page state is reset after each test and that whatever was done in one test, doesn't affect the results of a separate test.

For example, you can set a _before function on the test suite object in order to reset the localStorage before every test is run:

#!js
_tests.Suite._before = done => {
    Object.keys(localStorage).forEach(key => localStorage.removeItem(key));
    done();
};

These functions all take a single done argument which is a function that must be called once done.

_before

Runs before every test in a suite.

_after

Runs after every test in a suite.

_beforeSuite

Runs once, before any tests in a suite are run.

_afterSuite

Runs once, after all the tests in a suite have been run.

Package Sidebar

Install

npm i electro-func

Weekly Downloads

7

Version

2.0.2

License

MIT

Last publish

Collaborators

  • fyorl