🙃 대체왜요??
이거.. 좀 케케묵은 이슈이기도 하고..
스택오버플로우 보면 4년전에도 이런 일을 겪은 사람이 있고..
공식문서에서도 안내사항을 만들어둘만큼 유명한 이슈인 것 같은데요...
처음 시도하는 사람은 약간 신고식처럼 맞고가야하는 것 같네요
저는 마침 처음 시도해본 덕에 호되게 맞고왔습니다.
동작 자체는 문제가 없기 때문에 타입스크립트 사용자 한정으로 문제가 됩니다만..
요새 타입스크립트 안쓰는 사람이 어딨어요 하하
😙 왜 문제인가요?
자바스크립트 생태계의 테스트 프레임워크들이 여러개 존재한다는 것때문에 발생한다고 할 수 있습니다.
jest, vitest, mocha 등등등... 테스트 프레임워크는 참 많은 종류가 나와있고
또 얘네들은 사용자 편의를 위해 하나 익혀두면 다른것도 뭐 그냥저냥 쓸 수 있을만큼
문법이 유사합니다.
"유사"해요
https://docs.cypress.io/guides/references/bundled-libraries#Mocha
cypress는 위 문서에서도 예측할 수 있듯이
mocah의 구문을 사용하여 작성합니다. 다음 코드는 mocha 문법으로 작성된 cypress 코드입니다.
describe("so easy mocha test", () => {
it("엄청 쉽다 그죠 ", () => {
expect(true).to.equal(true);
});
});
그리고 이건 jest 문법으로 다시 작성한 동일 동작을 하는 테스트 코드입니다.
describe("이건 제스트에용", () => {
it("뭐가 다른 거임?", () => {
expect(true).toBe(true);
});
});
되게 비스무리한데 조금 다른 것을 볼 수 있습니다
mocha 구문을 이용하는 cypress의 expect 함수는 다음과 같은 타입을 지닙니다.
var expect: Chai.ExpectStatic
(val: any, message?: string) => Chai.Assertion
반면 jest의 expect 함수는 다음과 같은 타입을 지닙니다.
const expect: jest.Expect
<boolean>(actual: boolean) => jest.JestMatchers<boolean>
이름만 같을뿐 완전히 다른 타입이네요
😌 원인이 뭘까요?
node_modules/cypress/types/cypress-expect.d.ts
파일로 넘어가보면 다음과 같은 코드를 찾을 수 있습니다.
// Cypress adds chai expect and assert to global
declare var expect: Chai.ExpectStatic
declare var assert: Chai.AssertStatic
예.. expect와 assert를 전역적으로 선언하는 파일입니다.
덕분에 expect 함수를 사용하면 jest의 타입으로 추론되길 원하더라도
Chai.ExpectStatic 타입으로 추론되어 버리면서 jest 코드들이 망가지게 되는것이
원인이라고 할 수 있습니다.
jest의 expect 함수의 타입정의는 바로 여기에있습니다.
node_modules/@types/jest/index.d.ts
declare const expect: jest.Expect;
근데 여기서 궁금한게 생기지 않나요?
왜 번번이 var로 선언된 mocha의 타입이 jest 타입을 이기는걸까요?
declare에 이해가 깊지않고.. 그냥 대충 쓰다보니까 선언 키워드 별로 우선순위가 있는건지 궁금해졌습니다.
그래서 예제를 한번 만들어봤어요
const.d.ts
var.d.ts
let.d.ts
를 만들어두고 각 파일에서 똑같은 식별자의 타입을 선언하는 것입니다.
var.d.ts
declare var x: number;
const.d.ts
declare const x: boolean;
let.d.ts
declare let x: string;
include도 해주겠습니다.
tsconfig.json에서 include 프로퍼티에 값을 넣어주도록할게요
"include": ["var.d.ts" , "const.d.ts", "let.d.ts" ]
이제 example.ts를 만들고
x의 타입이 어떻게 추론되는지 구경해봅시다.
이걸 실험해보면서 꽤 많은 충격이 있었는데요..
😛 이거 외이럼??
var가 이긴것처럼 보입니다.
그런데 정말 사실일까요? 진짜로 var가 짱세서 const, let으로 선언한 이름들을 뭉개버리는걸까요?
라는 궁금함을 가지고 include의 순서를 바꾸어보기도하고
아예 모든 파일을 명시적으로 include 하지 않기도 해봤습니다.
그과정에서 대충 체감한 건 다음과 같은데요
1. 서로 동일한 식별자의 타입을 declare하고 있다면 파일명의 알파벳순서가 빠른 파일의 타입이 우선적용된다.
2. 간혹 혼란스럽게 타입이 적용되는 경우에는 타입스크립트 서버를 새로 켜보면 1규칙을 따르게 변한다.
3. 파일명이 더 빠른 경우에도 그 파일이 존재하는 폴더명이 느리다면 폴더명이 빠른 순으로 적용된다.
4. 선언하는 식별자 키워드는 그다지.. 영향을 끼치는 경우를 보지는 못한것같다.
근데.. 이거때문에 매번 cypress 타입 설정이 이기는지는 몰르겠네요
그냥 원인과 별개로 신기해서 알려드립니다.
😛 그럼 이거 어케 해결함?
그러면... 원인이 되는 코드를 찾았으면 그냥 그 코드 주석처리하면 해결 해피엔딩아님??
이라고 생각할 수 있습니다.
하지만.. 라이브러리 파일을 로컬에서만 건드려서 수정해버리는 것은
로컬환경에서만 통상적인 환경과 다른 환경을 가지게 되는 것이니
그건 그다지 좋은 방법이 아닐 것 같네요
찾아보니까 tsconfig.json파일을 폴더내부에 두는 형식으로 처리하면
특정 폴더만 다른 tsconfig를 받기때문에 해결을 할 수 있다는 요지의 답변을 스택오버플로우에서 찾았습니다.
이것을 적용해봅시다.
루트에서 지금까지 우리 프로젝트를 위해 열심히 일해준
tsconfig.json의 exclude 프로퍼티에 다음과 같이 cypress 관련 타입 설정들을 넣어주겠습니다.
"exclude": ["./cypress.config.ts", "node_modules", "cypress", "./cypress"]
그리고 cypress 코드들이 들어갈 cypress 폴더내부에 tsconfig.json을 생성해준뒤
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}
위와 같이 여기서는 cypress 타입을 사용하도록 설정을 주었습니다.
그 결과 이제 cypress 폴더 내부에서는 expect 함수의 타입이 Chai.ExpectStatic으로
그 외에는 expect가 jest.Expect 타입으로 적절히 추론되게 되어
행복하게 테스트코드를 짤 수 있는 환경을 가지게 되었다고 합니다.
😜 마치며
환경 세팅은 하다보면 느는 것 같읍니다..
이런일이 없는게 제일 좋지만... 마주치면... 해결할 줄 아는 사람이 되자..
'testcode' 카테고리의 다른 글
프론트엔드 테스트코드를 작성하면서 했던 고민들 (6) | 2024.05.13 |
---|---|
간단했던 모노레포 toBeInDocument 이슈 해결 (0) | 2024.01.07 |
jest --coverage 옵션을 통해 커버리지 확인하기 (1) | 2023.10.29 |
react-query, zustand test jest 환경설정 승리한 썰 푼다 (2) | 2023.10.26 |
[jest] jest에서 안쓰는 import가 있으면 테스트 에러를 내요.. (0) | 2023.08.02 |