Developer docs

Guide for VFS teams to update tests for Node 22

Last Updated: June 26, 2025

Platform is in the process of upgrading the version of Node used to build vets-website from 14 to 22. This guide is designed to help VFS teams migrate tests to run under Node 22. Many tests will not need updating; but some tests will fail under Node 22 due to changes in Node or other dependencies that have been updated.

Note: All pull requests for this work will need to be merged into the main branch in vets-website

Extension Change:

All unit test specs must end with .jsx in Node 22. Previously, specs could end with .js or .jsx. A linting will enforce this.

Mock Service Worker

To work with Node 22, MSW has been updated to version 2.x and this requires changes to any code that uses MSW:

node 14

node 22

import { rest } form 'msw'

import { http, HttpResponse } from 'msw'

JavaScript
rest.get(SOME_URL, (_res, res, ctx) => {
  return res(cts.status(200), ctx.json(jsonResponse)),
})
JavaScript
http.get(SOME_URL, () => {
  HttpResponse.json(jsonResponse, { status: 200 })
})
JavaScript
rest.get(SOME_URL, (req, res) => {
  return res.networkError('SIP Network Error');
})
JavaScript
http.get(SOME_URL, () => {
  return HttpResponse.error({
    status: 500,
    statusText: 'SIP Network Error'
  })
})
JavaScript
res.get(SOME_URL, (req, res, ctx) => {
  return res(ctx.status(404));
})
JavaScript
http.get(SOME_URL, () => {
  return HttpResponse(null, { status: 404 })
})
JavaScript
let expected;
before(() => {
  server.listen();
  server.events.on('request:end', async req => {
    expected = { ...expected, request: req }
  });
})
JavaScript
let expected;
before(() => {
  server.listen();
  server.events.on('request:end', async req => {
    expected = { ...expected, ...req }
  })
})

window.location

We have moved from using jsdom to happy-dom for Browser simulation and this requires some modifications to tests that access the window.location object.

node 14

node 22

global.window.location = URL(SOME_URL);

global.window.location.href = SOME_URL

JavaScript
global.window.location.origin = 'http://localhost';
global.window.location.pathname = '/sign-in/';
global.window.location.search = '?application=myvahealth';  
JavaScript
global.window.location.href = http://localhost/sign-in/application-myvahealth

expect(global.window.location).to.include(…)

expect(global.window.location.href).to.include

JavaScript
global.window = Object.create(global.window);
Object.assign(global.window, {
    dataLayer: [],
    location: {
      get: () => global.window.location,
      set: value => {
        global.window.location = value;
      },
      host: 'dev.va.gov',
      pathname: '',
      search: '',
    },
  });
JavaScript
global.window = window;
global.window.dataLayer = [];
global.window.location.href = 'https://dev.va.gov';
JavaScript
oldLocation = global.window.location;
delete global.window.location;
global.window.location = {
  replace: sinon.spy(),
}
JavaScript
oldLocation = global.window.location;
global.window.location.replace = sinon.spy();

Miscellaneous

  • If your component accesses sessionStorage or localStorage be sure to use window.sessionStorage or window.localStorage in the component or else component modifications will not be picked up in a test.

  • If your assertions are failing, try refactoring your test to use RTL and put the assertions inside a waitFor. For example, consider the following example of updating a test from node 14 to node 22:

node 14

node 22

JavaScript
const form = ReactTestUtils.renderIntoDocument(
  <DefinitionTester schema={definitions.bankAccount} uiSchema={uiSchema} />,
);

const formDOM = findDOMNode(form);
const find = formDOM.querySelector.bind(formDOM);
ReactTestUtils.Simulate.change(find('#root_routingNumber'), {
  target: {
    value: '123456789',
  },
});

ReactTestUtils.findRenderedComponentWithType(form, Form).onSubmit({
  preventDefault: f => f,
});
expect(find('.usa-input-error-message').textContent).to.equal(
  `Error ${uiSchema.routingNumber['ui:errorMessages'].pattern}`,
);
JavaScript
const form = render(
      <DefinitionTester schema={definitions.bankAccount} uiSchema={uiSchema} />,
    );

    const routingNumber = form.container.querySelector('#root_routingNumber');
    fireEvent.change(routingNumber, { target: { value: 123456789 } });

    const submitButton = form.getByRole('button', { name: 'Submit' });
    const mouseClick = new MouseEvent('click', {
      bubbles: true,
      cancelable: true,
    });

    fireEvent(submitButton, mouseClick);

    await waitFor(() => {
      const error = form.container.querySelector('.usa-input-error-message');
      expect(error.textContent).to.equal(
        `Error ${uiSchema.routingNumber['ui:errorMessages'].pattern}`,
      );
    });
  • In Node 22 the Response object is immutable after creation, whereas this was not the case in Node 14; make sure all options are included in the object at creation time as no error is thrown if you attempt to write to an immutable object (but the write will fail).

node 14

node 22

JavaScript
import { CONTACTS } from '@department-of-veterans-affairs/component-library';
JavaScript
import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
JavaScript
ReactTestUtils.Simulate.change(find('input'), {
    target: {
      value: 'asfd',
    },
  });

Note: the input in this case is of type “number”

JavaScript
const input = form.getByLabelText('Year');
Object.defineProperty(input, 'value', { 
  value: 'asdf',
  writable: true 
}); 

Note: cannot use normal RTL method to set the input’s value because the value here is not of the input’s type.


Help and feedback