#reactjs #typescript #jestjs #react-testing-library
#reactjs #typescript #jestjs #react-testing-library
Вопрос:
Я не могу подтвердить функцию selectXHandler
, когда пытаюсь протестировать ее напрямую. Поскольку она определена в частной области. Я не могу издеваться или шпионить за этой функцией, потому что вы не можете получить к ней доступ
export const CheckboxWrapper = ({
x,
y,
z,
a = [],
}: CheckboxWrapperProps) => {
const State = useContext(Context);
const [state, dispatch] = State;
const { Items } = state;
const selectXHandler = () => {
const payload = {
x,
a,
};
dispatch({ type: action, payload });
};
if (y) {
return (
<div className="mt5">
<Checkbox
checked={selectedItems[x] !== undefined}
label={z}
onChange={selectXHandler}
/>
</div>
);
}
return null;
};
const selectXHandler = jest.fn();
await fireEvent.click(checkbox);
expect(checkbox).toBeChecked();
expect(selectXHandler).toHaveBeenCalledTimes(1);
Я получаю следующую ошибку:
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
Комментарии:
1. вы должны тестировать поведение, ориентированное на общедоступность, а не внутренние детали реализации. Например. проверьте, как изменяются элементы вашего пользовательского интерфейса, а не то, какие внутренние функции вызываются
2. я смотрю на интеграционное тестирование, и проверка того, запущена функция или нет, является частью этого. @bsapaka
3. вместо этого вы можете имитировать redux и проверить, была ли вызвана его отправка и с какими аргументами. Это также ваш компонент «публичный интерфейс», в отличие от некоторой функции prviatefunction внутри. Предположим, вы переименовали свою внутреннюю функцию — может ли приложение быть сломано из-за этого? нет. С другой стороны, если ваш компонент начнет отправлять действие «A» вместо «B», это может привести к сбою вашего приложения. Так что имеет смысл покрыть это тестами.
4. @contextq независимо от того, что это за тест, он всегда должен тестировать публичный контракт, а не внутреннюю реализацию. Вы должны иметь возможность изменять любые внутренние компоненты таким образом, чтобы это удовлетворяло правильному поведению, и тест все равно должен пройти.
5. @skyboyer я не использую redux, вместо этого использую usereducer и context для создания чего-то подобного. Можете ли вы предложить в псевдокоде, как это было бы?
Ответ №1:
Как обсуждалось в комментариях выше, тестировать некоторые внутренние компоненты — плохой ход. Представьте, что это возможно, мы переименовали эту внутреннюю функцию, или встроили ее, или разделили на две. Завершится ли наш тест, который ее проверяет, ошибкой? Определенно. Будет ли наше приложение сломано? Конечно, нет.
Мы должны быть привязаны к общедоступному интерфейсу. Для компонента публичный интерфейс является:
import
s (например, входные значения)- реквизит (также входное значение)
- результат рендеринга (выходное значение)
- используемые контексты, если таковые имеются (также давайте рассматривать как входное значение)
Итак, я вижу тест следующим образом:
- для некоторых входных реквизитов
- мы имитируем нажатие на флажок
- и убедитесь, что
dispatch
из контекста был вызван какой-либо желаемый аргумент.
Я недостаточно знаю RTL, поэтому мой пример будет посвящен Enzyme. Но я уверен, что будет легко перевести это в соответствующие запросы селектора RTL.
import Context from '../where/its/placed/someContext.js';
it('dispatch is called with A on checkbox is clicked', () => {
const dispatch = jest.fn();
const state = {}; // or some initial state your test requires
const ourComponent = mount(<Content.Provider value={{ dispatch, state }}>
<CheckboxWrapper {...somePropsYouNeed} /></Content.Provider>);
ourComponent.find({label: 'some-label'}).simulate('change');
expect(dispatch).toHaveBeenCalledWith({ type: 'someType', payload: 'somePayload' });
expect(dispatch).toHaveBeenCalledTimes(1);
});
Комментарии:
1. это плохой ход для тестирования некоторых внутренних компонентов — это не так, это зависит от конкретного случая. Это плохо для функциональных компонентов, потому что они просто не были предназначены для этого. Я часто вижу, что «тестировать поведение, а не реализацию» рассматривается как религиозная догма, и это действительно плохой ход, IMO. Завершится ли наш тест, который ее проверяет, ошибкой? Определенно. Будет ли наше приложение сломано? Конечно, нет — почему это плохо? Во много раз лучше, чем тесты, которые не завершаются сбоем при поломке приложения. Если вы потеряете часы на исправление тестов, которые не должны завершаться неудачей, это плохо. Если утверждение внутренних компонентов экономит вам часы отладки, это хорошо. YMMV
2. @EstusFlask инжиниринг — это всегда компромиссы, согласен с вами. Но если начинающий разработчик с самого начала привязывает себя к тестированию внутренних компонентов, он в конечном итоге получит и то, и другое: ненадежные тесты и пустую трату времени на обслуживание из-за хрупкости. Также по пункту «почему это плохо?» — потому что (конечно, в зависимости от конкретного случая) это делает рефакторинг практически невозможным. Мало кому нравится боль, и большинство просто пропускают рефакторинг, который они могли бы выполнить, если бы им пришлось обновить более 10 тестов, чтобы удовлетворить рефакторингу с повышенным состоянием или методом перемещения в разделяемый помощник.
3. @EstusFlask на самом деле мой пример выше также является компромиссом (и я это понимаю) и частично полагается на внутренние данные (внутренние данные о том, как
Context
работает утверждение противdispatch
аргументов). Полностью независимый от внутренних будет скорее интеграционным и будет отображаться,<Provider><CheckboxWrapper /><AnotherComponentThatConsumesContext /></Provider>
когда мы запустим что-тоCheckbox
и проверим, изменен ли соответствующим образом другой компонент.