Home > Net >  jest: repeatedly mock navigator & test against userAgent/vendor
jest: repeatedly mock navigator & test against userAgent/vendor

Time:07-21

Intent:

  • want to cycle through different combinations of the userAgent
  • mock navigator
  • run test

What is happening:

  • I mock navigator.userAgent, mock happens as intended, first test runs as expected
  • 2nd mock is run, but the test sees the userAgent value from the first mock

function to test:

export const isBrowseriOS = () => {
  console.log(navigator.userAgent, ' & ', navigator.vendor);
  return (
    navigator.vendor === 'Apple Computer, Inc.' ||
    (/iPad|iPhone|iPod/.test(navigator.userAgent) &&
      !window.MSStream &&
      'ontouchend' in document)
  );
};

test file:

import { isBrowseriOS } from './isIOS';

const browserDevices = [
  {
    name: 'iPhone 12 Pro',
    userAgent:
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
    vendor: 'Apple Computer, Inc.',
    iOSDevice: true,
  },
   {
     name: 'Samsung SM-G955U',
     userAgent:
       'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',
     vendor: 'Google Inc.',
     iOSDevice: false,
   },
  
];

describe.each(browserDevices)('iOS test for $name', (device) => {
  beforeEach(() => {
    Object.defineProperty(window, 'navigator', {
      value: {
        userAgent: device.userAgent,
        vendor: device.vendor,
      },
    });
  });
  afterEach(() => jest.resetAllMocks());
  it(`returns ${device.iOSDevice}`, () => {
    expect(isBrowseriOS()).toBe(device.iOSDevice);
  });
});

result: enter image description here

CodePudding user response:

the key was using configurable: true, inside beforeEach and then resetting it to default inside afterEach;

working solution below:

function:

declare global {
  interface Window {
    MSStream?: any;
  }
}
export const isiOSDevice = () =>
  navigator.vendor === 'Apple Computer, Inc.' ||
  (/iPad|iPhone|iPod/.test(navigator.userAgent) &&
    !window.MSStream &&
    'ontouchend' in document);

test file:

import { isiOSDevice } from './isIOS';

const browserDevices = [
  {
    name: 'iPad Air',
    userAgent:
      'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',
    vendor: 'Apple Computer, Inc.',
    iOSDevice: true,
  },
  {
    name: 'Samsung SM-G955U',
    userAgent:
      'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',
    vendor: 'Google Inc.',
    iOSDevice: false,
  },
  {
    name: 'iPhone 12 Pro',
    userAgent:
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
    vendor: 'Apple Computer, Inc.',
    iOSDevice: true,
  },
  {
    name: 'Samsung SM-G981B',
    userAgent:
      'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36',
    vendor: 'Google Inc.',
    iOSDevice: false,
  },
  {
    name: 'Nest Hub Max',
    userAgent:
      'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.188 Safari/537.36 CrKey/1.54.250320',
    vendor: 'Google Inc.',
    iOSDevice: false,
  },
  {
    name: 'Windows Chrome',
    userAgent:
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
    vendor: '',
    iOSDevice: false,
  },
  {
    name: 'Windows Firefox',
    userAgent:
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0',
    vendor: '',
    iOSDevice: false,
  },
  {
    name: 'Windows edge',
    userAgent:
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62',
    vendor: 'Google Inc.',
    iOSDevice: false,
  },
];

describe.each(browserDevices)('iOS test for $name', (device) => {
  const { userAgent: originalUserAgent } = window.navigator;

  beforeEach(() => {
    Object.defineProperty(window, 'navigator', {
      configurable: true,
      writable: true,
      value: { userAgent: device.userAgent, vendor: device.vendor },
    });
  });

  afterEach(() => {
    Object.defineProperty(window, 'navigator', {
      configurable: true,
      value: originalUserAgent,
    });
  });

  it(`returns ${device.iOSDevice}`, () => {
    expect(isiOSDevice()).toBe(device.iOSDevice);
  });
});

  • Related