How to Refactor Legacy JS to Implement Unit Tests?

Asked

Viewed 173 times

3

I have a Wordpress site with many JS files that have not been structured to be tested - they have not been written as modules that can be imported nor is there a app.js that loads them all as a framework.

The files are only compiled and minified for use on the site, and I want to start rewriting little by little as I maintain the site, adding tests for solved bugs and new features.

All files have a structure similar to:

( function( window ) {
    'use strict';

    var document = window.document;

    var objeto = { 
        params : {
            // etc
        },
        init: function() {
            // etc
        },
        outroMetodo: function() {
            // etc
        }

    }

    objeto.init();
} )(this);

I was suggested to use Jest and the setup was very simple - the test environment is ready - but I don’t know how to load the files that need to be tested. My current configuration on package.json is this:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "verbose": true,
    "testMatch": [
      "<rootDir>/tests/jest/**/*.test.js"
    ]
  }
}

I imagine you need to refactor the files in some way to be able to load them into Jest before running the tests, but how would be the simplest way to allow this integration without rewriting the features? I tried using the settings setupFiles and setupTestFrameworkScriptFile but as I don’t have a single setup file it seems to me that is not the ideal option.

There is a way to include the file to be tested at the beginning of each test to test the methods?

include( '/arquivo.js' ); // pseudocodigo

describe("Testes para arquivo.js", function() {
    it("testes do metodo X", function() {
        expect(true).toBe(true);
    });
});
  • The example code that you gave is completely private, in order to test this, this self-invoking Function has to assign this "module" to the window - of some way. Then you just access the window.MODULE_NAME.metodo(arg) for testing; checka o module Pattern since this code is good looking and the refactor would not be too difficult :)

1 answer

1

You need four things to improve your code:

  1. A goal of how I would like your code to have organized (if you started from scratch, as you would do?)
  2. Every new code you create must obey its ideal structure (according to step 1).
  3. As you tap into old code, you refactor it so it fits into the new architecture.
  4. When the old code is 80%-90% refactored, you do the other 10%-20% in one shot.

In Javascript it is common that legacy code is written as a script, which is a big problem, because when importing the file to test it will run a lot of code (and this will happen only at the time of import).

To solve this problem, I suggest you refactor your code to separate it into two parts: Module and Script

Module is a piece of code where nothing runs. In it you create classes/functions and export them. Script is the type of code where you make modules run.

Example:

Module:

// module/auth.js
export default class Auth {
  authenticate(username, password) {
    // restante do código...
  }
}

Script:

// script/auth.js
import Auth from '../module/auth';

$(() => {
  $('form').on('submit', (evt) => {
    evt.preventDefault();

    const username = $('#username').val()
    const password = $('#password').val()

    const auth = new Auth()
    const result = await auth.authenticate(username, password);
    // restante do código...
  });
});

By doing this you can at least test module/auth.js, which is already a great start! If you want to go further, you can also define the part of jQuery (assuming you are using jQuery) also in a "modscript":

Modscript:

// modscript/auth.js
import Auth from '../module/auth';

export default () => {
  $('form').on('submit', (evt) => {
    evt.preventDefault();

    const username = $('#username').val()
    const password = $('#password').val()

    const auth = new Auth()
    const result = await auth.authenticate(username, password);
    // restante do código...
  });
}

Script:

// script/auth.js
import execute from '../modscript/auth';
$(() => execute());

This way you are now also able to test the interactions with DOM. Since you already use Jest, then you already have Jsdom installed and configured.

To test a "modscript" you do so:

import execute from '../modscript/auth';

let originalAuthenticate;

beforeEach(() => {
  originalAuthenticate = auth.authenticate;
  auth.authenticate = jest.fn();
});

afterEach(() => {
  auth.authenticate = originalAuthenticate;
  $('body').html(''); // clean document
});

it('authenticates user upon form submission', () => {
  // Arrange
  $('body').append('<form><input id="username" /><input id="password" /></form>');
  $('#username').val('spam');
  $('#password').val('egg');
  auth.authenticate.mockResolvedValue(true);

  // Act
  execute();
  $('form').trigger('submit');

  // Assert
  expect(auth.authenticate.mock.calls).toContain(['spam', 'egg']);
  // outros expects...
});

Architecting "modscripts" tests are considerably more complex than architecting module tests, but they give more confidence about their implementations because the scope of the tests will increase greatly.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.