Jest: window.location.href 不能在测试中更改。

创建于 2016-04-13  ·  70评论  ·  资料来源: facebook/jest

@cpojer

这实际上更像是一个jsdom@8问题...请参阅 tmpvar/jsdom#1388,但我也想在此处固定,以便 Jest 选择 jsdom 提出的任何解决方案。

以前使用[email protected]/[email protected]您可以编写如下测试:

jest.autoMockOff()
jest.setMock('../lib/window', window)

jest.mock('cookies-js')
jest.mock('query-string')
jest.mock('superagent')

describe(['@utils/auth - When an AJAX response returns:'
].join('')
, function () {

  beforeEach(function () {
    window.location.href = 'http://quux.default.com'
    var queryString = require('query-string')
    queryString.__setMockParseReturns([{
      'access_token': '1234foo',
      'expires_in': '9999'
    }])
  })

  it(['should set a redirect token and goto platform ',
    'when the AJAX request returns 401.'
  ].join('')
  , function () {
    var superagent = require('superagent')
    superagent.__setMockAjaxResponses([
      [null, { 'status': 401 }]
    ])

    var href = window.location.href
    var auth = require('../index.js')
    auth.login(function (res) {})
    var Cookies = require('cookies-js')
    var config = require.requireActual('../config')
    expect(decodeURIComponent(window.location.href)).toBe([
      config.loginUrl,
      config.loginServiceLogin,
      '?client_id=',
      config.clientId,
      '&response_type=token',
      '&redirect_uri=',
      config.clientRedirectUri
    ].join(''))
    expect(Cookies.__getMockCookieData()[config.clientId + '_state_locationAfterLogin']).toBe(escape(href))
  })

并且该测试将通过。 由于jsdom@8这不再可能并且这些测试失败。

似乎 jsdom 正在寻找某种类型的功能,只是想确保 Jest 会在可用时获取该功能。

最有用的评论

你是对的,这确实是一个 jsdom 问题。 在 Facebook,我们为解决这个问题所做的工作是使用这个:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

这对我们有用,但是我们仍然在内部使用 jsdom 7。

我会关闭这个,因为我相信Object.defineProperty的做事方式很好。 如果这在 jsdom 8 中对您不起作用,我很乐意重新打开它。

所有70条评论

你是对的,这确实是一个 jsdom 问题。 在 Facebook,我们为解决这个问题所做的工作是使用这个:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

这对我们有用,但是我们仍然在内部使用 jsdom 7。

我会关闭这个,因为我相信Object.defineProperty的做事方式很好。 如果这在 jsdom 8 中对您不起作用,我很乐意重新打开它。

酷,谢谢,我会试试这个。

@cpojer ,我似乎无法弄清楚我需要点击什么来重新打开这个问题......

无论如何,在jest环境中是否有人可以调用jsdom.changeUrl(window, url) ,如此处所述https://github.com/tmpvar/jsdom#sharing -the-url-of-an-existing-jsdom- [email protected]中的窗口实例?

旧票,但对于那些仍然有这个问题的人,我们已经开始使用window.location.assign()代替所以在我们的测试中我们可以像这样模拟assign函数..

it('will redirect with a bad route', () => {
    window.location.assign = jest.fn();
    const redirection = shallow(<Redirection />, {
      context: {
        router: {
          location: {
            pathname: '/wubbalubbadubdub',
          },
        },
      },
    });
    expect(window.location.assign).toBeCalledWith(`${CONFIG.APP_LEGACY_ROOT}`);
  });

谢谢@th3fallen 。 这很酷!

顺便说一句@cpojer我从 5 月 1 日开始在 FB.... ;P

好的!

我正在尝试将我们的测试从 Mocha+Chai+Sinon.js 迁移到 Jest,但不知道如何更改特定测试的位置。
Jest 19.x使用JSDom 9.12 ,它不允许使用Object.defineProperty技巧更改位置。 另外,由于 tmpvar/jsdom#1700 中描述的原因,我不能使用jsdom.changeURL()
@cpojer在 Jest 中为jsdom.changeURL()实现一些代理方法怎么样?

@okovpashko我们计划将 jsdom 暴露给环境: https ://github.com/facebook/jest/issues/2460

Object.defineProperty在 FB 为我们工作。

@thymikee我看到了这个问题,但认为这个提议被拒绝了。
@cpojer我误读了您的示例,并将其与其他与此问题相关的示例混在一起,人们建议使用Object.defineProperty(window, 'location', {value: 'url'}); 。 谢谢!

我不仅需要更改 href,因此我编写了简单的方法,这可能对阅读此线程的人有用:

const setURL = (url) => {
  const parser = document.createElement('a');
  parser.href = url;
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      value: parser[prop],
      writable: true,
    });
  });
};

很抱歉进一步拖出这个线程,但我已经尝试按照建议模拟推送功能......

reactRouterReduxMock.push = (url) => {
   Object.defineProperty(window.location, 'href', {
    writable: true,
    value: url
       })
})

但我仍然遇到一个似乎无法解决的 jsdom 错误:

       TypeError: Cannot read property '_location' of null
       at Window.location (/Users/user/projects/app/client/node_modules/jsdom/lib/jsdom/browser/Window.js:148:79)
       at value (/Users/user/projects/app/client/test/integration-tests/initialSetup.js:122:32) //this is the defineProperty line above

我意识到这是一个 jsdom 错误,但是对于那些已经解决了这个问题的人,您是否可以分享更多设置上下文来让我解决这个问题?

谢谢

@matt-dalton 在https://github.com/facebook/jest/issues/890#issuecomment -295939071 中尝试我的建议对我来说效果很好

@matt-dalton 你的网址是什么? 您是否在jest-config.json中设置了 testURL 或者它是否初始化为about:blank

@ianlyons是的,我在 package.json 中为此设置了"https://test.com/"的值,并且没有任何路径显示为blank

@th3fallen如果我理解正确,我认为这不适用于我的用例。 您是否将 url 作为导致触发分配的上下文值传递? 我正在尝试进行基本的集成测试,因此我想检查路由器如何响应初始数据负载。 我已经模拟了 API 响应,然后需要使用应用程序逻辑进行 URL 更改(即我不想自己在外部触发它)。

例如, Object.defineProperty似乎可以测试依赖于window.location.search的功能。 话虽如此,它会使window.location.search发生变异,因此其他测试可能会受到影响。 有没有办法通过Object.defineProperty “撤消”您对window.location.search所做的更改,有点像开玩笑的模拟函数有mockReset函数?

@msholty-fd 你可以试试这个方法:

const origLocation = document.location.href;
let location = origLocation;

beforeAll(() => {
  const parser = document.createElement('a');
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      get: function() {
        parser.href = location;
        return parser[prop];
      }
    });
  });
});

afterEach(() => {
  location = origLocation;
});

test('location 1', () => {
  location = "https://www.google.com/";
  console.log(document.location.href); // https://www.google.com/
});

test('location 2', () => {
  console.log(document.location.href); // about:blank
});

它在 Jest 22.0.1 中停止工作

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

错误信息:

TypeError: Cannot redefine property: href
        at Function.defineProperty (<anonymous>)

嗯,我们可能需要以某种方式允许人们调用reconfigurehttps://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring -the-jsdom-with-reconfiguresettings

打开一个与此相关的新问题,因为该问题已关闭:#5124

@SimenB我不相信 Jest 应该解决这个问题。 JSDOM 应该允许window.location.assign()按预期工作并重新配置window.location.href等的输出。

我得到TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL因为 jsdom 的基本 url 默认为about:blank

我试图为jsdom分配一个基本 url,花了 4 个小时没有成功(我知道怎么做,只需将<base href='your_base_url' />插入到 dom 中;但是,dom 是由jest创建的

Object.defineProperty解决方案仅适用于jsdom的旧版本(使用更高版本的jsdom会出现“无法重新定义属性错误”);
如果您使用的是jsdom ver > 10,那么@th3fallen提到的是正确的解决方案。
使用window.location.assign是正确的方法

如果您只想要about:blank以外的其他网址,则可以使用testURL配置。

感谢@SimenB的回复。

不,我说的是base url而不是url 。 我有可以执行window.location.href="/login"的代码,并且在运行jest时, jsdom抛出异常,抱怨/login不是有效的 url

TypeError: Could not parse "/login" as a URL

我检查了jsdom的源代码并意识到这是因为我没有基本 url 设置(这相当于在没有基本地址的浏览器 URL 栏中键入“/login”)。

使用jsdom ,通常我们可以通过

global.jsdom = new JSDOM('<html><head> <base href="base_url" /></head></html>')

但是因为jest设置了jsdom ,所以我们无法控制。
--- 更新:我想我可以显式添加jsdom作为依赖项并手动配置jsdom

然后我找到了一个解决方案,将window.location.href=替换window.location.assign并模拟assign函数,它对我有用

@bochen2014这个问题有更多关于如何使用新版本 jsdom 的信息:#5124

tl;dr:您可以模拟window.location.assign() ,或者您可以使用jest-environment-jsdom-global ,这将允许您在飞行中重新配置 jsdom。

谢谢@simon360

这就是我所做的;-)
我使用jsdom.reconfigure $ 在我的测试中设置不同的初始urls ,并且每当我需要更改代码中的 url(不是测试)时,我都会使用window.location.assign并对其进行模拟。 这对我有用。

仅适用于可能/将遇到相同问题的人,为您的 jsdom 设置 url

// jest.config.js
 module.exorts={ 
  testURL: 'http://localhost:3000',
  // or : 
  testEnvironmentOptions: {
     url: "http://localhost:3000/",
    referrer: "https://example.com/",
  }
}

请注意,这将为您的所有测试设置 url;
如果您想在某些特定测试中使用不同的 url,请使用jsdom.reconfigure api;
如果您需要在单元测试代码(即生产代码)之外即时更改 url,您需要使用window.location.assign并模拟它。

把它贴在其他票上,但我会把它贴在这里:

为 Jest 21.2.1 找到了不错的解决方案

好的,到目前为止,最简单的解决方案是:
进入您的 Jest 设置(例如,我将使用 package.json):

"jest": { "testURL": "http://localhost" }

现在您将可以访问 window 对象,然后您可以在测试期间将 URL 设置为您喜欢的任何内容。

it('Should set href url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = 'http://localhost/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'href', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.href);
});

it('Should set pathname url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = '/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'pathname', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.pathname);
});

希望这可以帮助某人。

@petar-prog91 这很有帮助。 你有一个错字 - 它应该是testURL而不是TestURL

@BarthesSimpson感谢您的通知,更新了评论。

停止发布此内容,它不适用于开玩笑”:“^22.4.2”

你好,
我在测试中使用了它,我删除了全局状态并使用 jsdom 创建了一个新状态...:

   describe('componentDidMount', () => {
    delete global.window
    const window = (new JSDOM(``, {url: 'https://example.org/'})).window
    global.window = window
    describe('When window is defined', () => {
      const spy = jest.spyOn(Utils, 'extractTokenFromUrl')
      it('should call extract token function with window location', () => {
        mount(<Header />)
        expect(spy).toHaveBeenCalledWith('https://example.org/')
      })
    })
  })

@UserNT确认 - 它给出TypeError: Cannot redefine property: href

@annemarie35无效 — ReferenceError: JSDOM is not defined

我不知道这是否会对某人有所帮助,但这就是我目前正在做的事情。

const redirectTo = (url: string): void => {
  if (process.env.NODE_ENV === "test") {
    global.jsdom.reconfigure({ url: `${getBaseUrl()}${url}` });
  } else {
    window.location.replace(url);
  }
};

编写一个重定向函数并使用它。 所以在测试环境中,它会依赖 jsdom.reconfigure url 来改变 url 部分。

我这样用

export const clientFetchData = (
  history: Object,
  routes: Object,
  store: Object
) => {
  const callback = location =>
    match({ routes, location }, (error, redirectLocation, renderProps) => {
      if (error) {
        redirectTo("/500.html");
      } else if (redirectLocation) {
        redirectTo(redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        if (!isEmpty(window.prerenderData)) {
          // Delete initial data so that subsequent data fetches can occur
          window.prerenderData = undefined;
        } else {
          // Fetch mandatory data dependencies for 2nd route change onwards
          trigger(
            FETCH_DATA_HOOK,
            renderProps.components,
            getDefaultParams(store, renderProps)
          );
        }

        trigger(
          UPDATE_HEADER_HOOK,
          renderProps.components,
          getDefaultParams(store, renderProps)
        );
      } else {
        redirectTo("/404.html");
      }
    });

  history.listen(callback);
  callback(history.getCurrentLocation());
};

之后,在你的测试中,它可以是这样的

    describe("# match route", () => {
      it("should navigate to error page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](true);
        expect(window.location.href).toEqual(`${SERVER_URL}/500.html`);
      });

      it("should redirect to /hello-world.html page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](undefined, {
          pathname: "/hello-world.html",
          search: ""
        });
        expect(window.location.href).toEqual(`${SERVER_URL}/hello-world.html`);
      });
...

我最终这样做了:

global.window = new jsdom.JSDOM('', {
  url: 'http://www.test.com/test?foo=1&bar=2&fizz=3'
}).window;

我在 JSDOM 设置文件的顶部有这个:

const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
  url: "http://test.com"
});
const { window } = jsdom;

function copyProps(src, target) {
  const props = Object.getOwnPropertyNames(src)
    .filter(prop => typeof target[prop] === 'undefined')
    .map(prop => Object.getOwnPropertyDescriptor(src, prop));
  Object.defineProperties(target, props);
}

global.document = window.document;
global.window = window;
global.navigator = {
  userAgent: 'node.js',
};

global.HTMLElement = window.HTMLElement;

通过在 Jest 配置中设置“testURL”:“ http://localhost/ ”来修复它(我使用的是最新版本)。 默认情况下它是“ about:blank ”,它会导致 JSDOM 错误(您不能将“about:blank”url 更改为其他内容)。

资源:
http://jestjs.io/docs/en/configuration#testurl -string
https://github.com/jsdom/jsdom/issues/1372

我发现这篇文章很有帮助: https ://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

“在您的 Jest 配置中,请确保设置以下内容:

"testURL": "https://www.somthing.com/test.html"

然后在测试的 beforeEach() 部分中,根据需要使用更改路径
历史.pushState()。

window.history.pushState({}, 'Test Title', '/test.html?query=true');

瞧! 现在,您可以更改任何测试的路径,而不必像其他人在上面提到的线程中建议的那样覆盖任何 jsdom 配置。 不确定我在哪个线程上找到了这个解决方案,但对发布它的开发人员表示敬意!”

@Mike-Tran 你摇滚! 这完全有效,如此简单。 我什至不必使用 testURL 设置。

@Mike-Tran 那行得通! 谢谢! 但是,我不需要testURLbeforeEach 。 我已经做了:

window.history.pushState({}, 'Test Title', '/test.html?query=true');

现在我不必再使用Object.defineProperty了😅

@jcmcneal谢谢你帮我做的! (开玩笑 23.0.0)

如果您的目标是模拟window对象,这是我的(不是那么优雅,但它有效)解决方案:

创建一个接口(不确定接口是否是正确的词,但我希望你明白这一点)类:

// window.js
export const { location } = window;

在您的实际代码中,将window替换为接口方法,例如win

// myFile.js
import * as win from './window';

export function doSomethingThatRedirectsPage() {
  win.location.href = 'google.com';
}

然后,在你的笑话测试中,你只是将它们模拟出来,这样 jsdom 就不会抱怨了。 你甚至可以断言它们:

// myFile.test.js
import * as myFile from './myFile';
import * as win from './window';

it('should redirect', () => {
  win.location = { href: 'original-url' };

  expect(win.location.href).toBe('original-url');

  myFile.doSomethingThatRedirectsPage();

  expect(win.location.href).toBe('google.com');
});

@Mike-Tran,@ jcmcneal谢谢! 所有的作品都一样!

类 SSOtestComponent 扩展 React.Component {

componentDidMount() {
    let isSuccess = this.props.location.pathname === '/sso/test/success' ? true : false

    window.opener.postMessage({ type: "sso_test", isSuccess,...this.props.location.query}, window.location.origin)
}

onSsoAuthenticate() {

}

componentWillUnmount() {
}

render() {
    return (<Loader />);
}

}

module.exports = SSOtestComponent;

我正在使用 enjyme 和 jest 编写单元测试用例,如何编写条件 window.location ...请给出答案

这对我有用

    const location = JSON.stringify(window.location);
    delete window.location;

    Object.defineProperty(window, 'location', {
      value: JSON.parse(location)
    });

    Object.defineProperty(global.location, 'href', {
      value: 'http://localhost/newURL',
      configurable: true
    });

在玩笑版本 23.6.0

这对我有用。

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

@FelipeBohnertPaetzold谢谢

谢谢@FelipeBohnertPaetzold。 我在我的代码中使用location.host ,所以发现我需要一个完整的位置对象,所以以下对我来说效果更好,而不必手动传递每个位置属性:

delete global.window.location;
global.window.location = new URL("https://www.ediblecode.com/");

请注意,这适用于 Node 6.13+(请参阅URL 类文档)并且我使用的是 Jest 24。

另请注意,这不适用于相对 URL,请参阅https://url.spec.whatwg.org/#example -url-parsing。

这个TypeScriptJest 24.0.0和 Node 10.15.0上为我工作:

src/setupTests.ts

import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');

src/setupTests.test.ts

describe('setup tests', () => {

    describe('window.location', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            window.location.assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            (window.location.assign as jest.Mock<void, [string]>).mockClear();
        });

        it('location.replace replaces a location', () => {
            window.location.replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            (window.location.replace as jest.Mock<void, [string]>).mockClear();
        });

        it('location.reload is a spy', () => {
            window.location.reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            (window.location.reload as jest.Mock).mockClear();
        });
    });
});

src/testUtils.ts

interface MockedLocation extends Location {
    assign: jest.Mock<void, [string]>;
    reload: jest.Mock;
    replace: jest.Mock<void, [string]>;
}

interface MockedWindow extends Window {
    location: MockedLocation;
}

export function mockWindow(win: Window = window, href = win.location.href) {
    const locationMocks: Partial<MockedLocation> = {
        assign: jest.fn().mockImplementation(replaceLocation),
        reload: jest.fn(),
        replace: jest.fn().mockImplementation(replaceLocation),
    };

    return replaceLocation(href);

    function replaceLocation(url: string) {
        delete win.location;
        // tslint:disable-next-line:no-any
        win.location = Object.assign(new URL(url), locationMocks) as any;
        return win as MockedWindow;
    }
}

src/testUtils.test.ts

import { mockWindow } from './testUtils';

describe('test utils', () => {

    describe('mockWindow', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            const { assign } = mockWindow().location;
            assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            assign.mockClear();
        });

        it('location.replace replaces a location', () => {
            const { replace } = mockWindow().location;
            replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            replace.mockClear();
        });

        it('location.reload is a spy', () => {
            const { reload } = mockWindow().location;
            reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            reload.mockClear();
        });
    });
});

@jedmao

嘿,伙计)很棒的工具!

对我来说, src/setupTests.test.ts中的测试有点多余,因为您已经在 src/testUtils.test.ts 中完全测试mockWindow util。 因此,在src/setupTests.ts的测试中,只需使用正确的参数调用mockWindow $ 就足够了。

谢谢)

@tzvipm @jup-iter 感谢 👍。 我刚刚发布@jedmao/storage@jedmao/location ,它们都与 Jest 完全无关。 您应该能够spyOn适当的方法而无需编写任何额外的测试,因为 npm 包已经过完全测试。

如果您在使用 Vue 时遇到此错误,只需使用this.$router.push({...})而不是this.$router.go({...})

image

将下面的代码放在第 1 行:
delete global.window.location;
global.window.location = "";

现在可以捕获正在更改 window.location 的单击事件。

“笑话”:“^23.6.0”
v10.15.0
6.5.0

这有效:

delete window.location;
window.location = Object.assign({}, window.location);
const url = Object.assign({}, new URL('http://google.com'));
Object.keys(url).forEach(prop => (window.location[prop] = url[prop]));

或者更好...

delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;

你是对的,这确实是一个 jsdom 问题。 在 Facebook,我们为解决这个问题所做的工作是使用这个:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

这对我们有用,但是我们仍然在内部使用 jsdom 7。

我会关闭这个,因为我相信Object.defineProperty的做事方式很好。 如果这在 jsdom 8 中对您不起作用,我很乐意重新打开它。

是的,我有一些函数处理location.searchlocation.hash ,我想用你提到的defineProperty来测试它。 它不会工作!

当我关闭jest静音模式时,我发现: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

现在我不知道如何测试我的功能。

任何人都有办法改变测试url

你是对的,这确实是一个 jsdom 问题。 在 Facebook,我们为解决这个问题所做的工作是使用这个:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

这对我们有用,但是我们仍然在内部使用 jsdom 7。
我会关闭这个,因为我相信Object.defineProperty的做事方式很好。 如果这在 jsdom 8 中对您不起作用,我很乐意重新打开它。

是的,我有一些函数处理location.searchlocation.hash ,我想用你提到的defineProperty来测试它。 它不会工作!

当我关闭jest静音模式时,我发现: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

现在我不知道如何测试我的功能。

任何人都有办法改变测试url

在我的情况下,在jest.config.js文件中有一个字段testURL可能有效。 但是如果我想在每次测试之前更改 testURL 怎么办。

我发现这篇文章很有帮助: https ://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

“在您的 Jest 配置中,请确保设置以下内容:

"testURL": "https://www.somthing.com/test.html"

然后在测试的 beforeEach() 部分中,根据需要使用更改路径
历史.pushState()。

window.history.pushState({}, 'Test Title', '/test.html?query=true');

瞧! 现在,您可以更改任何测试的路径,而不必像其他人在上面提到的线程中建议的那样覆盖任何 jsdom 配置。 不确定我在哪个线程上找到了这个解决方案,但对发布它的开发人员表示敬意!”


优秀的解决方案!!! 非常感谢你! @迈克-特兰
我想要一个像这样的简短且非侵入性的解决方案!

为了在 2019 年 6 月开始工作,我必须这样做:

    delete global.window.location;
    global.window = Object.create(window);
    global.window.location = {
      port: '123',
      protocol: 'http:',
      hostname: 'localhost',
    };

我用这个……

window.history.pushState({}, '', `${url}/`);

可能我的 JSDOMTestWrapper 的一部分可以帮助某人

    /** <strong i="6">@type</strong> {Window} */
    this.testWindowObject = Object.create(window);
    const wnd = this.testWindowObject;
    this.testWindowObject.history = {
      state: null,
      prev: { /** <strong i="7">@todo</strong> immutable stack with the go(step) method emulation */
        state: null,
        pathname: null,
        search: null,
      },
      go(step) {
        logger.special('history go called', step);
        logger.warn('history go has not supported yet');
      },
      back() {
        this.state = this.prev.state;
        wnd.location.pathname = this.prev.pathname;
        wnd.location.search = this.prev.search;
        const eventData = this.state ? { url: this.state.displayURL, newState: this.state, type: 'push' } : null;
        wnd.sm.eventsService.triggerEvent(ROUTER_EVENTS.ROUTE_PUSH, eventData);
        wnd.sm.urlService.simpleRouteTo(`${ wnd.location.pathname || '' }${ wnd.location.search || '' }`);
        logger.special('history back emulated');
      },
      pushState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('push state emulated', { state, title, url });
      },
      replaceState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('replace state emulated', { state, title, url });
        logger.special('test: urlService.getPathName()', wnd.sm.urlService.getPathName());
      },
    };
    this.testWindowObject.innerWidth = WND_WIDTH;
    this.testWindowObject.innerHeight = WND_HEIGHT;
    this.testWindowObject.fetch = fetchFn;
    this.testWindowObject.localStorage = lstMock;
    this.testWindowObject.scrollTo = (x, y) => {
      /** not implemented yet https://github.com/jsdom/jsdom/issues/1422 */
      if (typeof x !== 'number' && (x.left || x.top)) {
        y = x.top;
        x = x.left;
      }
      // logger.info(`window.scrollTo(${ x }, ${ y })`);
    };

    if (fetchFn === JSDOMTestWrapper.FETCH_FN.DEV_MOCK) {
      global.Request = RequestMock;
      this.testWindowObject.Request = RequestMock;
    }

    if (href) {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location, urlapi.parse(href));
    }
    else {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location);
    }

    (function(ELEMENT) {
      ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
      ELEMENT.closest = ELEMENT.closest || function closest(selector) {
          if (!this) return null;
          if (this.matches(selector)) return this;
          if (!this.parentElement) {return null}
          else return this.parentElement.closest(selector)
        };
      ELEMENT.getBoundingClientRect = ELEMENT.getBoundingClientRect || (() =>
        ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 }));
    }(Element.prototype));

    this.testWindowObject.getBoundingClientRect = () =>
      ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 });

    this.testWindowObject.__resizeListeners__ = [];
    this.testWindowObject.__resizeTriggers__ = {};
    this.testWindowObject._detectElementResize = {
      removeResizeListener: () => {},
    };

    this.testWindowObject.matchMedia = jest.fn().mockImplementation(query => {
      return {
        matches: false,
        media: query,
        onchange: null,
        addListener: jest.fn(),
        removeListener: jest.fn(),
      };
    });

    this.rftpr = () => {};
    this.mode = mode;
    this.renderFirstTimePromise = new Promise((resolve) => {
      this.rftpr = resolve;
    });

    this.marpr = () => {};
    this.mobileAppReadyPromise = new Promise((resolve) => {
      this.marpr = resolve;
    });

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      this.testWindowObject.navigator = Object.assign({}, this.testWindowObject.navigator, {
        language: storeKey,
        appVersion: '5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        vendor: 'Google Inc.',
      });

      global.intercom = {
        registerUnidentifiedUser: jest.fn(),
        registerForPush: jest.fn(),
      };
    }

    const XApp = mode ? MobileApp : App;
    const app = <XApp window={ this.testWindowObject } rftResolve={ this.rftpr } storeKey={ storeKey } apiHost={ apiVersion } forceMobileDetection={ mode } />;
    render(app, this.testWindowObject.document.body);

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      setTimeout(() => {
        this.testWindowObject.sm.deviceService.appRestorePathHasInit = this.marpr;
        this.testWindowObject.sm.deviceService.fireEvent(this.testWindowObject.document, 'deviceready');
      }, 200);
    }

此方法自 2019 年 9 月 27 日起有效: https ://stackoverflow.com/a/54034379/1344144

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
    value: {
       href: url
    },
    writable: true
});

另一个解决方案目前对我有用,而无需编写jsdom

  1. 确保您在jest.config.js中设置testURL ,无论值如何:
// jest.config.js
'testURL': 'https://someurl.com'

在您的测试文件中:

window.history.pushState({}, 'Mocked page title', 'www.yoururl.com');

学习自: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking。 感谢瑞恩!

不适合我:

TypeError:在严格模式下不允许分配给只读属性

image

it("should save hash when history is not found", () => {
    const historyBkp = global.window.history;

    delete global.window.history;
    global.window.history = false;

    externalLoader.savePageURL(urlTraining);

    expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);

    global.window.history = historyBkp;
    window.location.hash = "";
});

不适合我:

TypeError:在严格模式下不允许分配给只读属性

image

it("should save hash when history is not found", () => {
  const historyBkp = global.window.history;

  delete global.window.history;
  global.window.history = false;

  externalLoader.savePageURL(urlTraining);

  expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);

  global.window.history = historyBkp;
  window.location.hash = "";
});

将此添加到全局文件中。

删除 global.window.location;
全局.window.location = "";

此方法自 2019 年 9 月 27 日起有效: https ://stackoverflow.com/a/54034379/1344144

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
    value: {
       href: url
    },
    writable: true
});

我正在尝试使用 location.assign 做类似的事情,但似乎这不再起作用了。

这在 jest 24.9.0 上对我有用

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

这在 jest 24.9.0 上对我有用

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

我必须使代码异步才能使其正常工作,因为我在 Promise 中运行代码。

所以现在工作😃

如何在 vuex 操作中测试 chenge 位置?

async setForm({ rootState, state, commit, dispatch }, formData) {
          ....
          switch (answ.result.type) {
            ....
            case 'redirect':
              console.log(answ.data.url);
              window.location = answ.data.url;
              console.log({ location: window.location.href });
              break;
            default:
              break;
it('setForm - success, redirect', async done => {
      expect(window.location.href).toBe('https://www.google.ru/');

我有错误:

    expect(received).toBe(expected) // Object.is equality

    Expected: "https://www.google.ru/"
    Received: "http://localhost/"

这对我有用

    const location = JSON.stringify(window.location);
    delete window.location;

    Object.defineProperty(window, 'location', {
      value: JSON.parse(location)
    });

    Object.defineProperty(global.location, 'href', {
      value: 'http://localhost/newURL',
      configurable: true
    });

在玩笑版本 23.6.0

什么是全局?

全局定义在哪里?

这对我有用。

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

这将创建一个具有所有原始功能的位置,但它是可模拟的:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
此页面是否有帮助?
0 / 5 - 0 等级