javascript – How to refactor legacy JS to implement Unit Tests?

Question:

I have a WordPress site with a lot of JS files that weren't structured to be tested – they weren't written as modules that can be imported, nor is there an app.js that loads them all like a framework.

The files are only compiled and minified for use on the site, and I want to start revamping it little by little as I keep up with the site, adding tests for fixed 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);

They suggested using Jest and the setup was pretty simple – the test environment is ready – but I don't know how to load the files that need to be tested. My current configuration in package.json is this:

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

I imagine you need to refactor the files somehow to be able to load them into Jest before running the tests, but what would be the simplest way to allow this integration without rewriting the functionality? I tried using the setupFiles and setupTestFrameworkScriptFile settings but as I don't have a single setup file it doesn't seem like the ideal option.

Is there 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);
    });
});

Answer:

You need four things to improve your code:

  1. A goal of how you would like your code to be organized (if you started from scratch, how would you?)
  2. Any new code you create must conform to its ideal structure (according to step 1).
  3. As you tap into old code, you refactor it to fit the new architecture.
  4. When the old code is 80%—90% refactored, you do the other 10%—20% in one fell swoop.

In JavaScript, it's common for legacy code to be written as a script, which is a big problem because when you import the file for testing it will run a lot of code (and this will only happen 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 is executed. 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 jQuery part (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());

That way you are now also able to test interactions with the DOM. As you already use Jest, then you already have JSDom installed and configured.

To test a "modscript" you do like this:

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 modscript tests are considerably more complex than architecting module tests, but they give you more confidence in your implementations because the scope of tests will increase significantly.

Scroll to Top
AllEscort