웹 스토리지 유틸 함수에 대한 테스트 코드를 작성하다가 조금 헤매고 난 후에 해결해서 그 과정을 글로 남기고자 합니다.
스토리지 유틸 함수를 만든 이유
스토리지 API를 이용해서 바로 특정 값을 가져올 수도 있을 텐데요.
그럼에도 불구하고, 유틸 함수를 별도로 만드는 이유는 스토리지가 문자열 값만 저장하기 때문입니다.
특히 스토리지에 객체를 JSON 형태의 문자열로 저장하는 것이 유용할 텐데요.
객체를 바로 스토리지에 저장하게 되면 [object Object]
형태의 문자열로 변환되어 저장되기 때문에 유틸 함수에서 JSON 문자열로 변환시켜서 스토리지에 저장하려는 것입니다. (toString의 동작방식 때문)
// 스토리지 유틸 함수 예시
export const getSessionStorageItem = (key: string) => {
const storageItem = sessionStorage.getItem(key) || '';
if (!storageItem) {
return null;
}
try {
return JSON.parse(storageItem);
} catch (e) {
console.error(e);
return storageItem;
}
};
export const setSessionStorageItem = (key: string, value: string) => {
if (!key || !value) {
throw new Error('잘못된 key를 전달했습니다.');
}
sessionStorage.setItem(key, value);
};
세션 스토리지의 메소드를 모킹하면 될까?
위의 예제에서 유틸 함수 내부에 sessionStorage
의 getItem
, setItem
을 사용하여 기능을 구현하고 있습니다.
sessionStorage
의 getItem
과 setItem
을 모킹해서 어떤 객체에 값을 읽거나 쓰도록 해서 테스트를 구현하면 되지 않을까? 라는 생각이 듭니다.
// storageUtil.test.ts
// 각 테스트 suite 실행되기 전에 스토리지 모킹 & 셋팅
beforeAll(() => {
const storage: Record<string, any> = {
wrongKey: ' ',
goodKey: 1,
};
sessionStorage.getItem = jest.fn((key: string) => {
// 모킹한 함수가 실행되는지 로그로 확인
console.log('/// getItem ///');
return storage[key];
});
sessionStorage.setItem = jest.fn((key: string, value: string) => {
// 모킹한 함수가 실행되는지 로그로 확인
console.log('/// setItem ///');
storage[key] = value;
});
});
// getSessionStorageItem 테스트
describe('getSessionStorageItem', () => {
it('key에 대한 값을 반환한다.', () => {
expect(getSessionStorageItem('goodKey')).toBe(1);
});
});
// setSessionStorageItem 테스트
describe('setSessionStorageItem', () => {
it('key에 대한 value값을 세션스토리지에 저장한다.', () => {
setSessionStorageItem('key', 'key');
expect(getSessionStorageItem('key')).toBe('key');
});
});
위와 같이 sessionStorage
의 getItem
, setItem
을 모킹하고, 혹시 몰라서 모킹 함수 내부에 로그를 넣었습니다.
테스트를 돌려보면, 로그가 찍히지 않고 getItem
과 관련된 테스트는 실패하게 되는데요.
왜 이런 현상이 발생하는 지 명확한 원인을 찾기가 쉽지 않습니다...
(세션 or 로컬) 스토리지는 Storage 객체의 인스턴스 일뿐...
크롬 브라우저에서 sessionStorage
가 어떤 프로퍼티를 가지고 있는 지 확인하고 있습니다.
보시면 sessionStorage
내부가 아닌 Storage.prototype
객체에 스토리지의 메소드들이 구현이 되어 있습니다.
즉, 로컬 스토리지와 세션 스토리지는 Storage 객체의 인스턴스일 뿐이며, 자체적으로 메소드가 없기 때문에 getItem
, setItem
을 모킹해도 기대했던 동작을 하지 않았던 것입니다. (로컬 스토리지나 세션 스토리지에서 메소드를 호출할 때는 프로토타입 체이닝으로 호출)
2가지 방법으로 스토리지를 모킹할 수 있습니다.
첫 번째 방법은 localStorage
또는 sessionStorage
자체를 모킹하는 것입니다.
별도의 객체를 만들어서 getItem
, setItem
등의 메소드를 구현해볼 수 있을 텐데요.
좀 번거롭다는 부분이 있습니다.
두 번째 방법은 Storage.prototype
의 메소드를 모킹하는 것입니다.
Storage.prototype.getItem
, Storage.prototype.setItem
부분만 모킹하면 되기 때문에 위의 방법보다는 단순하게 테스트를 검증해볼 수 있겠습니다. 두 번째 방법으로 테스트 코드를 작성해보겠습니다..!
// storageUtil.test.ts
// 여기 부분만 바꿔주면 되겠네요..!
beforeAll(() => {
const storage: Record<string, any> = {
wrongKey: ' ',
goodKey: 1,
};
Storage.prototype.getItem = jest.fn((key: string) => {
console.log('/// getItem ///');
return storage[key];
});
Storage.prototype.setItem = jest.fn((key: string, value: string) => {
console.log('/// setItem ///');
storage[key] = value;
});
});
// getSessionStorageItem 테스트
describe('getSessionStorageItem', () => {
...
});
// setSessionStorageItem 테스트
describe('setSessionStorageItem', () => {
...
});
beforeAll
내부의 코드만 변경해주면 될 것 같네요.
혹시 몰라서, 모킹 함수 내부에 로깅을 남겨 보겠습니다.
테스트 코드를 돌려보면, 로그가 정상적으로 찍히고 테스트 구문(Suite)들이 정상적으로 통과하게 됩니다.
'개발이슈' 카테고리의 다른 글
[Jest] 테스트 작성 시, window.alert 모킹하기 (jest.fn, jest.spyOn) (0) | 2023.08.05 |
---|---|
[개발이슈] Storybook에서 SVG 이슈 발생 (Element type is invalid ... ) (0) | 2023.07.06 |
[GitLab] The pipeline failed due to the user not being verified 이슈 해결 (0) | 2023.03.09 |
[Next.js] Component selectors can only be used in conjunction 이슈 (0) | 2022.08.07 |
[Next.js] Jest + Cypress 환경에서 Cypress 제공함수의 타이핑 & 자동완성이 안되는 경우 (0) | 2022.04.29 |