1

I have a couple of Philip Hue lights in the hallway. These show the build status on their lights with a small Node.JS application. Its working like a charm.

Currently I am in the process of testing and refactoring this application, but in my app.js I have some lines of code where I am having a hard time to figure out how to automatically test them.

if(config.enabledHueUpdateJobs) {
    var Hue = require('./hue.js');
    var Lights = require('./lights.js');

    var hallwayLights = new Lights(new Hue());
    hallwayLights.scheduleOn('45 7 * * 1-5');
    hallwayLights.scheduleOff('30 17 * * 1-5');    
    hallwayLights.scheduleUpdate('*/30 * 7-18 * * 1-5', buildServer.getFailedBuildCount);
}

I have 100% coverage for the Hue.js and the Lights.js 'classes', but the actual implementation code is not covered. I have had an issue where I changed the 'classes' public methods, but not the implementation code, but this was not caught by my tests.

I have been thinking about moving this code to a 'class' with a .start() function, but then still I would have one or two untested lines.

Please suggest a way how I can restructure my code (or some best practices/patterns that I should research/use) to make this better testable.

Update:

  • I want to let our build-server deploy a working version after all my tests have run. So I don't have to check the system manually.
  • I would like to have a TDD workflow, I would like to stay in my IDE and be confident to push my final changes.
  • Preferably I do not want to start 'node app.js' and test it with some blackbox testing system
  • 1
    Um, how do you actually get feedback from a Phillip Hue light to prove that your tests work? Are you using some sort of calibrated color sensor? – Robert Harvey Nov 17 '16 at 17:28
  • In addition, what value do such tests bring to the table that a visual inspection of the output of the bulb doesn't already provide? – Robert Harvey Nov 17 '16 at 17:28
  • I don't really think I need feedback from the lights, I am already mocking the API. Also I do not want to test the lamps have the right color, I trust the Hue API to work and be wel tested itself. – Niels van Reijmersdal Nov 17 '16 at 19:13
  • I just want to make sure when I am adding a new feature TDD style or when refactoring that I don't forget to update the code in app.js. For example I could rename the Lights class to HallwayLamps, because I think it reads better, all my tests would succeed on the build-server and the build-server will auto deploy to the production system. But if I forgot to update the app.js the system is broken. I just want to cover the actual implementation with tests so I can safely update the system without manual testing it. – Niels van Reijmersdal Nov 17 '16 at 19:13
  • 1
    RobertHarvey, thanks for calling it an integration test. Laiv, thanks for your ideas. I think parameterizing the interface helped to push me into my current solution. – Niels van Reijmersdal Nov 18 '16 at 10:33
0

I found a working solution to write an integration-test for my code. I will try to explain my implementation.

I have added a way to start and stop my code in app.js. (I was already using a similar strategy for my Selenium tests for another project) This way I can start and stop the app.js code from the tests, without starting 'node app.js' from the command-line.

app.js code:

exports.start = function () {
    buildServer.updateCaches();

    var hallwayLights = new Lights(new Hue(config.hueServerUrl, config.hueUserName));
    jobs.push(hallwayLights.scheduleOn(config.lightsOnSchedule));
    jobs.push(hallwayLights.scheduleOff(config.lightsOffSchedule));    
    jobs.push(hallwayLights.scheduleUpdate(config.lightsUpdateSchedule, buildServer.getFailedBuildCount)); 
}

exports.close = function() {
    jobs.forEach(function (job) {
        job.cancel();
    }); 
}

if (!isStartedFromTests()) { exports.start(); }
function isStartedFromTests() { return module.id != require.main.id; }

In the tests (using ava) I nock all the expected external API calls and verify they have been called.

test.integration.js code:

test.cb('Radiator: Should execute light schedules When the app is started', t => {
    // Arrange
    t.plan(3);
    var bsNocks = new buildServerNocks(config.buildserverServerName, config.buildserverPort);
    bsNocks.buildSuccess('Project1_Build1');
    bsNocks.buildSuccess('Project1_Build2');
    bsNocks.buildFailed('Project2_SubProject_Build3');

    var light1 = nock(config.hueServerUrl + config.hueUserName).put('/lights/1/state', hue.colors['red']).reply(200);
    var light2 = nock(config.hueServerUrl + config.hueUserName).put('/lights/2/state', hue.colors['green']).reply(200);
    var light3 = nock(config.hueServerUrl + config.hueUserName).put('/lights/3/state', hue.colors['green']).reply(200);

    // Act
    app.start();

    // Assert
    setTimeout(function() {
        t.is(light1.done())
        t.is(light2.done())
        t.is(light3.done())
        app.close();        
        t.end();
    }, 1200);
});

In the development.js config file I set the cron-schedule to every second, so it tests very fast. I moved the actual configs to production.js. This configuration setup is described in this blog post.

lightsUpdateSchedule: '*/1 * * * * *'

I have verified it works as expected by changing some internal function names like scheduleOn to jobOn and see that the test does fail.

Although it works, I am not yet convinced this is the best and most readable solution. Still open for other answers or idea's.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.