Издевательство над `документом` в шутку

Я в шутку пытаюсь писать тесты для своих проектов веб-компонентов. Я уже использую babel с пресетом es2015. У меня проблема при загрузке файла js. Я следил за фрагментом кода, в котором объект document имеет объект currentScript. Но в тестовом контексте это null. Так что я подумывал поиздеваться над тем же. Но jest.fn() на самом деле не помогает. Как я могу справиться с этой проблемой?

Кусок кода, в котором шутка терпит неудачу.

var currentScriptElement = document._currentScript || document.currentScript;
var importDoc = currentScriptElement.ownerDocument;

Я написал тестовый пример. component.test.js

import * as Component from './sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

Ниже приведена ошибка, вызванная шуткой.

Test suite failed to run

    TypeError: Cannot read property 'ownerDocument' of null

      at src/components/sample-component/sample-component.js:4:39

Обновление: согласно предложению Андреаса Кёберле, я добавил несколько глобальных переменных и попытался издеваться над следующим

__DEV__.document.currentScript = document._currentScript = {
  ownerDocument: ''
};
__DEV__.window = {
  document: __DEV__.document
}
__DEV__.document.registerElement = jest.fn();

import * as Component from './arc-sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

Но не повезло

Обновление. Я пробовал приведенный выше код без __dev__. Также, установив документ как global.


person thecodejack    schedule 12.12.2016    source источник
comment
Вы пробовали использовать global.document?   -  person Pavithra Kodmad    schedule 13.12.2016
comment
да .. я пробовал это .. не повезло ..   -  person thecodejack    schedule 14.12.2016
comment
Так что я в основном использовал jsdom, например const jsdom = require('jsdom'); const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>'; global.document = jsdom.jsdom(documentHTML); И после этого я добавляю все, что хочу, к документу и его доступному в моих тестах.   -  person Pavithra Kodmad    schedule 15.12.2016
comment
на самом деле проблема в том, что jsdom очень прост и не имеет API веб-компонентов. В любом случае временно разрешил это в соответствии с моим ответом.   -  person thecodejack    schedule 16.12.2016


Ответы (9)


Подобно тому, что говорили другие, но вместо того, чтобы пытаться самостоятельно издеваться над DOM, просто используйте JSDOM:

mocks /client.js

import { JSDOM } from "jsdom"
const dom = new JSDOM()
global.document = dom.window.document
global.window = dom.window

Затем в вашей конфигурации шутки:

    "setupFiles": [
      "./__mocks__/client.js"
    ],
person Patrick Lee Scott    schedule 31.05.2018
comment
они, должно быть, изменили свой API год назад? - person Patrick Lee Scott; 06.06.2019
comment
похоже, new JSDOM() все еще верен - может у вас что-то еще настроено неправильно, например babel? github.com/jsdom/jsdom - person Patrick Lee Scott; 06.06.2019
comment
они не помещают журналы изменений с выпусками, которые так сложно сказать github.com/jsdom/jsdom/releases - person Patrick Lee Scott; 06.06.2019
comment
Журнал изменений находится по адресу github.com/jsdom/jsdom/blob/master/Changelog. мкр. - person Alf Eaton; 27.06.2019
comment
Да, вам нужны babel-jest и @ babel / plugin-transform-modules-commonjs. Он по-прежнему работает с Jest 24 (я использую ts-jest) и отлично подходит для имитации других глобальных переменных, таких как window.navigator или навигатор :-) - person barbara.post; 22.01.2020

Я решил это с помощью свойства setUpFiles в шутку. Это будет выполняться после jsdom и перед каждым тестом, что идеально для меня.

Задайте setupFiles в конфигурации Jest, например:

"setupFiles": ["<rootDir>/browserMock.js"]


// browserMock.js
Object.defineProperty(document, 'currentScript', {
  value: document.createElement('script'),
});

Идеальной ситуацией была бы загрузка webcomponents.js для полифилляции jsdom.

person thecodejack    schedule 16.12.2016

Я боролся с издевательским документом для проекта, над которым я работаю. Я вызываю document.querySelector() внутри компонента React, и мне нужно убедиться, что он работает правильно. В конечном итоге это сработало для меня:

it('should test something', () => {
    const spyFunc = jest.fn();
    Object.defineProperty(global.document, 'querySelector', { value: spyFunc });
    <run some test>
    expect(spyFunc).toHaveBeenCalled()
});
person strausd    schedule 27.09.2018

Если вы, как и я, хотите смоделировать документ до undefined (например, для тестов на стороне сервера / на стороне клиента), я смог использовать object.defineProperty внутри своих тестовых наборов без необходимости использовать setupFiles

Пример:

beforeAll(() => {
  Object.defineProperty(global, 'document', {});
})
person tctc91    schedule 20.02.2018
comment
Я пытался издеваться над document.referrer и document.URL, указав их в объекте ... не повезло. Свойства по-прежнему отображались с их обычными значениями в средстве выполнения тестов. - person theUtherSide; 19.05.2018
comment
@theUtherSide Я заметил, что это решение работает в некоторых версиях Node, а не в других. Возможно, установите NVM и поиграйте с разными версиями - person tctc91; 19.05.2018
comment
Спасибо за совет! Я поиграю с версией Node и опубликую некоторые выводы! Мне эта стратегия действительно нравится больше, чем использование конфигурации Jest. Я работаю в большой команде, и наши модульные тесты лучше держать изолированными и, конечно, атомарными! - person theUtherSide; 19.05.2018
comment
Подтверждаю - приложение, в котором я это пробовал, использует Node v8.11.1 и Jest 21.2.1. - person theUtherSide; 21.05.2018
comment
TypeError: Cannot redefine property: document = ›` в JSDOMEnvironment.teardown (node_modules / jest-environment-jsdom / build / index.js: 192: 14) ` - person Dimitri Kopriwa; 15.02.2020

Если вам нужно определить тестовые значения для свойств, существует несколько более детальный подход. Каждое свойство нужно определять индивидуально, также необходимо сделать свойства writeable:

Object.defineProperty(window.document, 'URL', {
  writable: true,
  value: 'someurl'
});

См .: https://github.com/facebook/jest/issues/890.

У меня это сработало, используя Jest 21.2.1 и Node v8.11.1

person theUtherSide    schedule 18.05.2018
comment
Спасибо. Так. Много. ‹3 - person Striped; 19.02.2020

Это структура моего проекта под названием суперпроект внутри папки суперпроект:


  • super-project
    • config
      • __mocks__
        • dom.js
    • src
      • user.js
    • tests
      • user.test.js
    • jest.config.js
    • package.json

Вам нужно настроить Jest, чтобы использовать макет в ваших тестах:

dom.js:

import { JSDOM } from "jsdom"
const dom = new JSDOM()
global.document = dom.window.document
global.window = dom.window

user.js:

export function create() {
  return document.createElement('table');  
}

user.test.js:

import { create } from "../src/user";

test('create table', () => {
  expect(create().outerHTML).toBe('<table></table>');
});

jest.config.js:

module.exports = {
  setupFiles: ["./config/__mocks__/dom.js"],
};

Ссылки:

Вам необходимо создать макет вручную:
https://jestjs.io/docs/en/manual-mocks.html

Управление объектом DOM:
https://jestjs.io/docs/en/tutorial-jquery

Конфигурация Jest:
https://jestjs.io/docs/en/configuration

person danilo    schedule 17.07.2020

Я мог бы решить эту же проблему, используя модуль global scope на nodejs, установив документ с макетом документа, в моем случае getElementsByClassName:

// My simple mock file
export default {
    getElementsByClassName: () => {
        return [{
            className: 'welcome'
        }]
    }
};

// Your test file
import document from './name.component.mock.js';
global.document = {
    getElementsByClassName: document.getElementsByClassName
};
person Renato B.    schedule 28.01.2017
comment
да ... это так, но это очень простой вариант использования ... в webcomponents.js у нас есть большой набор взаимосвязанных API. Издеваться над ними - чертовски трудная задача ... - person thecodejack; 31.01.2017
comment
Но что именно вам нужно в mock? - person Renato B.; 22.02.2017

Нашел другое решение. Допустим, внутри вашего компонента вы хотите получить ссылку на элемент в DOM по имени класса (document.getElementsByClassName). Вы могли сделать следующее:

let wrapper
beforeEach(() => {
    wrapper = mount(<YourComponent/>)
    jest.spyOn(document, 'getElementsByClassName').mockImplementation(() => 
        [wrapper.find('.some-class').getDOMNode()]
    )
})

Таким образом, вы вручную устанавливаете возвращаемое значение getElementsByClassName равным ссылке на .some-class. Возможно, потребуется повторно визуализировать компонент, вызвав wrapper.setProps ({}).

Надеюсь, это поможет некоторым из вас!

person Marnix.hoh    schedule 17.12.2020

Надеюсь это поможет

const wrapper = document.createElement('div');
const render = shallow(<MockComponent{...props} />);
document.getElementById = jest.fn((id) => {
      wrapper.innerHTML = render.find(`#${id}`).html();
      return wrapper;
    });
person vnxyz    schedule 02.01.2020
comment
Этот ответ предполагает, что компонент является компонентом React. Я не вижу ничего, что указывало бы на это. Мне это кажется обычным веб-компонентом javascript. - person Greg Malcolm; 04.03.2020