2023.08.18 - [source-code/FrontEnd] - Jest를 통한 unit test
Jest를 통해 특정 Class내 method가 의도대로 동작하는지에 대한 단위 테스트를 작성했었다.
하지만 (특히) 프론트엔드 개발자가 테스트하고 싶은 항목은
- 특정 상황에서 의도한 화면이 잘 뜨는지
- 사용자 액션 발생 (클릭, 타이핑, 스크롤 등) 시 의도대로 동작하는지
- 비동기 작업 발생 시 화면의 의도대로 표시되는지
와 같은, UI적 요소가 대부분일 테다!
https://testing-library.com/docs/react-testing-library/intro/
이전에 사용했던 Jest와 React Testing Library를 사용,
DOM을 기준으로 사용자의 행위 기반 테스트를 작성해 볼 예정!
설치
현재 서비스는 React Native + Expo 환경으로 구성되어 있다.
https://docs.expo.dev/develop/unit-testing/
expo 공식 문서 내 unit testing 항목을 참고하며 환경세팅을 진행했다.
yarn add --dev jest-expo jest
다음은 testing-library/react를 설치할 차례인데...
@testing-library/react 의 render 함수를 통해 컴포넌트를 렌더링 할 경우
React Native는 브라우저 환경에서 동작하는 것이 아니기 때문에, 해당 DOM을 올바르게 구성하지 못하는 에러가 발생한다.
https://github.com/callstack/react-native-testing-library
yarn add --dev @testing-library/react-native
따라서 @testing-library/react-native를 사용!
yarn add --dev react-dom react-test-renderer
react-dom과 react-test-renderer도 추가적으로 설치했다.
package.json 설정
// package.json
"scripts": {
...
"test": "jest"
},
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
]
}
@testing-library/react-native를 사용할 경우,
preset을 "@testing-library/react-native"로 설정하는 게 일반적이다.
그러나 이 경우 expo 의존 모듈 호출 시 테스트가 실패하게 되는데,
현 프로젝트에는 이미 수많은 expo 라이브러리가 설치되어 있었다.
(expo-font, expo-secure-store 등등등...)
Jest의 테스트 환경에서는 해당 의존성 모듈에 대한 정의가 존재하지 않으므로
jest.mock("expo-font");
jest.mock("expo-secure-store");
...
엄청난 양의 라이브러리 함수들을 직접 mocking 해줘야만 했다.
→ preset을 jest-expo로 설정할 경우, 해당 모듈이 포함된 테스트도 정상적으로 동작할 수 있게 된다.
trouble shooting
Provider
redux Provider를 사용 중이라면, render를 통해 컴포넌트 호출시 에러가 발생한다.
(useSelector는 provider 내부에 존재해야 한다는 에러)
https://redux.js.org/usage/writing-tests
친절한 공식문서 → renderWithProviders를 작성해, 앱 내에서 사용 중인 state를 넘겨 감싸주면 간단히 해결된다.
이와 유사한 에러가
react-query, react-navigation 등을 사용해 발생했고,
역시 유사한 해결 방법(render 함수 내 wrapper 옵션에 필요한 컴포넌트들을 wrapping)으로 해결할 수 있었다.
export function renderWithAllWrappers(
ui,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return (
<NavigationContainer>
<QueryClientProvider client={queryClient}>
<Provider store={store}>{children}</Provider>
</QueryClientProvider>
</NavigationContainer>
);
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
expo-font
테스트 코드를 통해 의도한 text가 정상적으로 DOM에 그려지는지를 확인했다.
import { render } from "@testing-library/react-native";
describe("<Test />", () => {
it("renders correctly", () => {
const tree = render(<Test />);
const elementWithText = tree.getByText("테스트");
expect(elementWithText).toBeTruthy();
});
});
다음과 같이 작성했지만... 실패한 테스트.
분명 해당 텍스트가 화면에 존재하는데
테스트 환경 DOM에는 Text 컴포넌트가 정말로 존재하지 않았다.
import { Text } from "react-native";
import { useFonts } from "expo-font";
const Text = ({children}) => {
const [loaded] = useFonts({
Font: require("./Font.otf"),
});
if (!loaded) {
return null;
}
return <Text>{children}</Text>
}
원인은? 앱 내 font 적용을 위해 useFonts를 통해 폰트를 적용한 Text 컴포넌트를 사용하고 있었는데...
해당 hooks가 비동기로 동작하면서, loaded가 false인 상태로 테스트가 시작-종료돼버린 것.
jest.mock("expo-font", () => ({
useFonts: jest.fn().mockReturnValue([true]), // 반환값에 loaded: true를 포함
}));
해당 hooks를 mocking 해, 항상 true를 반환할 수 있도록 수정했다.
(해당 폰트가 잘 불러와졌는지는 테스트의 관심사가 아니라 판단)
styled-component
styled-component로 작성된 스타일 컴포넌트 모든 곳에서 에러가 발생했다!
TypeError: Cannot read properties of undefined (reading 'withConfig') 에러가 발생했는데...
import styled from "styled-components";
...
const StyledView = styled.View`
...
`
위와 같이 styled를 사용해 발생한 문제였다.
import styled from "styled-components";
import { View } from 'react-native'
...
const StyledView = styled(View)`
...
`
위와 같이 직접 react-native의 View 컴포넌트를 직접 상속하거나
import styled from "styled-components/native";
...
const StyledView = styled.View`
...
`
styled-components/native에서 styled를 import 해 해결할 수 있었다.
'source-code > FrontEnd' 카테고리의 다른 글
vanilla extract 사용하기 - 기본 (0) | 2023.08.30 |
---|---|
testing-library/react-native를 통한 컴포넌트 테스트 2 (0) | 2023.08.18 |
aria-label 을 통한 웹 접근성 향상 (0) | 2023.08.18 |
image decode method를 통한 콘텐츠 scrollHeight 계산 (0) | 2023.08.18 |
응집도 (0) | 2023.08.17 |