Home > OS >  Mock window.location in TypeScript jest test
Mock window.location in TypeScript jest test

Time:10-06

I've got a piece of code the puts together a full URL for a redirection (something like this):

import { redirect } from './some-utils'

export const goToURL = () => {
    const url = window.location.origin   window.location.pathname
    redirect(url)
}

Now, I'm trying to write a TypeScript test that tests the URL string:

describe('my-test-file', () => {
    let originalWindowLocation
    const redirect = jest.fn()

    beforeEach(() => {
        jest.resetAllMocks()
        originalWindowLocation = window.location
    })

    afterEach(() => {
        window.location = originalWindowLocation
    })

    it('test that redirection URL is correct', () => {
        delete window.location // can't do this because TS complains
        window.location = { origin: 'https://www.example.com', pathname: '/mypath' } // can't do this because TS complains

        goToURL()
        expect(redirect).toHaveBeenCalledTimes(1)
        expect(redirect).toHaveBeeenCalledWith('https://www.example.com/mypath')
    })
})

However, I get two TypeScript errors. On the delete line:

The operand of a 'delete' operator must be optional.

and on the new assignment of window.location:

Type '{ origin: string; pathname: string; }' is not assignable to type 'Location | (string & Location)'. Type '{ origin: string; pathname: string; }' is not assignable to type 'string & Location'. Type '{ origin: string; pathname: string; }' is not assignable to type 'string'.

I tried fixing the first error by removing the delete statement and tried fixing the second error by adding as Location to the end of the assignment. Doing so fixes the TS errors but my test no longer passes. It uses the domain of my web app instead of the example one in my test.

Can anyone help me fix up my TS errors while ensuring that my tests pass?

Edit:

If I try window.location = 'https://www.example.com/mypath', my test still does not pass and I still get a TS error:

Type 'string' is not assignable to type 'Location | (string & Location)'

If I try window.location.href = 'https://www.example.com/mypath', the TS errors go away but the test does not pass.

If I try window.location.assign(https://www.example.com/mypath'), the TS errors go away but the test does not pass.

CodePudding user response:

If you are only using a subset of the Location API that's also available on an instance of URL (e.g. window.location.href = "https://domain.tld/pathname"), then you can manually mock (replace) that property on window during your tests:

describe('description', () => {
  let originalWindowLocation = window.location;

  beforeEach(() => {
    Object.defineProperty(window, 'location', {
      configurable: true,
      enumerable: true,
      value: new URL(window.location.href),
    });
  });

  afterEach(() => {
    Object.defineProperty(window, 'location', {
      configurable: true,
      enumerable: true,
      value: originalWindowLocation,
    });
  });

  it('test that redirection URL is correct', () => {
    const expectedUrl = 'https://www.example.com/mypath';
    window.location.href = expectedUrl;
    expect(window.location.href).toBe(expectedUrl);
  });
});

I intentionally did not address the other aspects of your code because it's not what you asked about, but if you want to mock a function invocation (e.g. redirect) in a closure (e.g. goToURL) from an external module, you'll need to mock that function. See mocking modules and mocking partials in the Jest documentation.

CodePudding user response:

Mock window.location.assign

This is the way I've done it.

It sure is convenient to simply cram a new string into window.location or window.location.href, but that is not amenable to testing.

Rule 0 of testing code: the code must be written such that it can be tested.

So, switch to window.location.assign(url) in your app code.

But, to verify that the app really did attempt to change the URL, you must spy on the assign method during the test, like so:

let assignSpy = jest.spyOn(window.location, 'assign')

// ...arrange, act

expect(assignSpy).toHaveBeenCalledWith( theUrlYouExpect )

Your test can't simply inspect the current window.location or window.location.href. The entire purpose of switching to the assign method is because we can spy on methods. That is, we switched from a technique that works but is untestable, to a different technique that has the same effect and is testable.

CodePudding user response:

Remove the delete since this is a read-only property.

MDN says you can change the origin and the pathname with location.replace(). Here's an example

location.replace('http://example.com/my-path');
  • Related