From dd52cb790d1b830d424f43abe04400260f35a478 Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Wed, 6 Jan 2021 18:54:17 +0900 Subject: [PATCH 1/7] Update fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 테스트에 사용되는 다양한 fixture를 수정한다. --- fixtures/product.js | 24 +-- fixtures/products.js | 148 ++++++++++-------- fixtures/user.js | 4 +- .../container/ProductContainer.test.jsx | 9 +- .../presentational/ArticleProfile.test.jsx | 5 +- .../presentational/ProductDetail.test.jsx | 7 +- src/pages/HomePage.test.jsx | 5 +- src/pages/ProductPage.test.jsx | 18 ++- 8 files changed, 129 insertions(+), 91 deletions(-) diff --git a/fixtures/product.js b/fixtures/product.js index 4b6198d..686e32b 100644 --- a/fixtures/product.js +++ b/fixtures/product.js @@ -1,15 +1,21 @@ const product = { - title: '아이패드 4세대 팝니다.', - description: '신형 아이패드 4세대 12.9인치 팝니다. 사용한지 3개월도 안된 제품이에요.', - region: '서울 강남', - price: '990000', + id: 1, + category: '가구/인테리어', + createAt: '168828208635', + description: '해외 이민으로 인해 가전제품 팝니다!010-****-****로 연락주세요!', + price: 350000, productImages: [ - 'https://via.placeholder.com/600/810b14', - 'https://via.placeholder.com/600/24f355', - 'https://via.placeholder.com/600/f66b97', + { name: '가전1.jpeg', imageUrl: '가전1url' }, + { name: '가전2.jpeg', imageUrl: '가전2url' }, + { name: '가전3.jpeg', imageUrl: '가전3url' }, ], - createAt: '12341231242', - creatorId: 'abc123', + region: '서울시 강남구', + title: '해외 이사로 인한 가전제품 팔아요!', + user: { + displayName: '홍길동/연구과제팀/직원', + email: 'ghdrlfehd@test.com', + uid: 'ghdrlfehd1234', + }, }; export default product; diff --git a/fixtures/products.js b/fixtures/products.js index 6089416..6c142aa 100644 --- a/fixtures/products.js +++ b/fixtures/products.js @@ -1,95 +1,119 @@ const products = [ { - user: { - email: 'tester@example.com', - displayName: '홍 길동', - uid: '1234abcd', - }, id: 1, - title: '크리넥스 KF-AD 소형 마스크 팝니다.', - category: '기타 중고물품', + category: '가구/인테리어', + createAt: '1608828208635', + description: '해외 이민으로 인해 가전제품 팝니다!010-****-****로 연락주세요!', + price: 350000, productImages: [ - { name: 'test1', imageUrl: 'testImageUrl1' }, - { name: 'test2', imageUrl: 'testImageUrl2' }, - { name: 'test3', imageUrl: 'testImageUrl3' }, + { name: '가전1.jpeg', imageUrl: '가전1url' }, + { name: '가전2.jpeg', imageUrl: '가전2url' }, + { name: '가전3.jpeg', imageUrl: '가전3url' }, ], - region: '미추홀구 용현5동', - price: '25,000', - description: '저 크리넥스 스타일 마스크 대형 3개입\n님 크리넥스 데일리 입체형 황사마스크 KF80 소형 2개 교환 합니다... Or KF80 KFAD 소형 2개 가능합니다\n\n크리넥스 스타일 블랙 3매입 100장(75000) 판매도 해요\n', - }, - { + region: '서울시 강남구', + title: '해외 이사로 인한 가전제품 팔아요!', user: { - email: 'tester@example.com', - displayName: '홍 길동', - uid: '1234abcd', + displayName: '홍길동/연구과제팀/직원', + email: 'ghdrlfehd@test.com', + uid: 'ghdrlfehd1234', }, + }, + { id: 2, - title: '알레르망 범퍼침대', - category: '가구/인테리어', + category: '여성의류', + createAt: '1609754642862', + description: '팝니다.220 사이즈에요.', + price: 25000, productImages: [ - { name: 'test4', imageUrl: 'testImageUrl4' }, - { name: 'test5', imageUrl: 'testImageUrl5' }, - { name: 'test6', imageUrl: 'testImageUrl6' }, + { name: '신발1.jpeg', imageUrl: '신발1url' }, ], - region: '연수구 송도동', - price: '60,000', - description: '알레르망 범퍼 침대입니다\n18년도 구입했고 친정에서 주말에 방문할때만 사용해서 상태 좋습니다\n친정 이사로 인해\n처분합니다\n세탁해서 깨끗한 김장봉투에 넣어\n정리해 둔 상태입니다\n', - }, - { + region: '인천 남구', + title: '여성 운동화', user: { - email: 'tester@example.com', - displayName: '홍 길동', - uid: '1234abcd', + displayName: '김 철수', + email: 'rlacjftn@test.com', + uid: 'rlacjftn1234', }, + }, + { id: 3, - title: '청바지', category: '남성패션/잡화', + createAt: '1609763267254', + description: '청바지 팝니다.32 사이즈 청바지에요.', + price: 25000, productImages: [ - { name: 'test7', imageUrl: 'testImageUrl7' }, - { name: 'test8', imageUrl: 'testImageUrl8' }, - { name: 'test9', imageUrl: 'testImageUrl9' }, + { name: '청바지1.jpeg', imageUrl: '청바지1url' }, ], - region: '청학동', - price: '10,000', - description: '게스 청바지 사이즈 25입니다\n하자없습니다\n가격내림', - - }, - { + region: '서울 강남', + title: '청바지 팝니다.', user: { - email: 'tester@example.com', - displayName: '홍 길동', - uid: '1234abcd', + displayName: '김 철수', + email: 'rlacjftn@test.com', + uid: 'rlacjftn1234', }, + }, + { id: 4, - title: '아이패드 에어3', category: '디지털/가전', + createAt: '1608838040530', + description: '아이패드 에어 중고 팔아요64기가 와이파이 버전입니다.', + price: 250000, productImages: [ - { name: 'test10', imageUrl: 'testImageUrl10' }, - { name: 'test11', imageUrl: 'testImageUrl11' }, - { name: 'test12', imageUrl: 'testImageUrl12' }, + { name: '아이패드1.jpeg', imageUrl: '아이패드1url' }, + { name: '아이패드2.jpeg', imageUrl: '아이패드2url' }, + { name: '아이패드3.jpeg', imageUrl: '아이패드3url' }, ], - region: '연수구 연수3동', - price: '650,000', - description: '펜슬 아이패드 파우치 아이패드 케이스 펜슬케이스 펜슬촉 다드립니다', + region: '서울', + title: '아이패드 에어 팔아요.', + user: { + displayName: '홍길동/연구과제팀/직원', + email: 'ghdrlfehd@test.com', + uid: 'ghdrlfehd1234', + }, }, +]; + +const userProducts = [ { + id: 1, + category: '가구/인테리어', + createAt: '1608828208635', + description: '해외 이민으로 인해 가전제품 팝니다!010-****-****로 연락주세요!', + price: 350000, + productImages: [ + { name: '가전1.jpeg', imageUrl: '가전1url' }, + { name: '가전2.jpeg', imageUrl: '가전2url' }, + { name: '가전3.jpeg', imageUrl: '가전3url' }, + ], + region: '서울시 강남구', + title: '해외 이사로 인한 가전제품 팔아요!', user: { - email: 'tester@example.com', - displayName: '홍 길동', - uid: '1234abcd', + displayName: '홍길동/연구과제팀/직원', + email: 'ghdrlfehd@test.com', + uid: 'ghdrlfehd1234', }, - id: 5, - title: '겔럭시S7 핑크블로썸 팝니다.', + }, + { + id: 4, category: '디지털/가전', + createAt: '1608838040530', + description: '아이패드 에어 중고 팔아요64기가 와이파이 버전입니다.', + price: 250000, productImages: [ - { name: 'test13', imageUrl: 'testImageUrl13' }, - { name: 'test14', imageUrl: 'testImageUrl14' }, - { name: 'test15', imageUrl: 'testImageUrl15' }, + { name: '아이패드1.jpeg', imageUrl: '아이패드1url' }, + { name: '아이패드2.jpeg', imageUrl: '아이패드2url' }, + { name: '아이패드3.jpeg', imageUrl: '아이패드3url' }, ], - region: '학익1동', - price: '100,000', - description: '✔통신사 sk로 쓰던 폰이며 색깔은 딸기우유색입니다\n✔정상해지 완료 한 제품입니다 😊\n✔32기가 입니다\n✔케이스 생활을 해도 생활기스가 약하게 있는 점 이해부탁드려요\n생활기스는 가까이서 보시면 티가 납니다😊\n✔거래 장소는 학익동 인천향교유림회관 앞입니다!\n✔중고제품이다 보니 환불 안되시는 점 주의해주세요!', + region: '서울', + title: '아이패드 에어 팔아요.', + user: { + displayName: '홍길동/연구과제팀/직원', + email: 'ghdrlfehd@test.com', + uid: 'ghdrlfehd1234', + }, }, ]; export default products; + +export { userProducts }; diff --git a/fixtures/user.js b/fixtures/user.js index c55f85e..31a2af3 100644 --- a/fixtures/user.js +++ b/fixtures/user.js @@ -1,7 +1,7 @@ const logInUser = { - uid: '1234abcd', displayName: '홍 길동', - email: 'tester@example.com', + email: 'ghdrlfehd@example.com', + uid: 'ghdrlfehd1234', }; const logOutUser = { diff --git a/src/components/container/ProductContainer.test.jsx b/src/components/container/ProductContainer.test.jsx index fa2cf77..5b6f24a 100644 --- a/src/components/container/ProductContainer.test.jsx +++ b/src/components/container/ProductContainer.test.jsx @@ -5,7 +5,7 @@ import { useSelector, useDispatch } from 'react-redux'; import ProductContainer from './ProductContainer'; -import products from '../../../fixtures/products'; +import product from '../../../fixtures/product'; jest.mock('react-redux'); @@ -29,7 +29,7 @@ describe('ProductContainer', () => { } context('with product', () => { - given('product', () => products[0]); + given('product', () => product); it('dispatchs called', () => { renderProductContainer(); @@ -38,10 +38,11 @@ describe('ProductContainer', () => { }); it('renders product', () => { + const { title, region } = product; const { container } = renderProductContainer(); - expect(container).toHaveTextContent('크리넥스 KF-AD 소형 마스크 팝니다.'); - expect(container).toHaveTextContent('미추홀구 용현5동'); + expect(container).toHaveTextContent(title); + expect(container).toHaveTextContent(region); }); }); }); diff --git a/src/components/presentational/ArticleProfile.test.jsx b/src/components/presentational/ArticleProfile.test.jsx index b66e9ff..a1980e3 100644 --- a/src/components/presentational/ArticleProfile.test.jsx +++ b/src/components/presentational/ArticleProfile.test.jsx @@ -8,11 +8,12 @@ import { logInUser } from '../../../fixtures/user'; describe('ArticleProfile', () => { it('render seller profile', () => { + const { displayName, email } = logInUser; const { container } = render(( )); - expect(container).toHaveTextContent('홍 길동'); - expect(container).toHaveTextContent('tester@example.com'); + expect(container).toHaveTextContent(displayName); + expect(container).toHaveTextContent(email); }); }); diff --git a/src/components/presentational/ProductDetail.test.jsx b/src/components/presentational/ProductDetail.test.jsx index f868798..ae05076 100644 --- a/src/components/presentational/ProductDetail.test.jsx +++ b/src/components/presentational/ProductDetail.test.jsx @@ -5,6 +5,7 @@ import { render, fireEvent } from '@testing-library/react'; import ProductDetail from './ProductDetail'; import products from '../../../fixtures/products'; +import product from '../../../fixtures/product'; describe('ProductDetail', () => { function renderProductDetail({ product }) { @@ -16,10 +17,10 @@ describe('ProductDetail', () => { } it('renders product detail', () => { - const { container } = renderProductDetail({ product: products[0] }); + const { container } = renderProductDetail({ product }); - expect(container).toHaveTextContent('크리넥스 KF-AD 소형 마스크 팝니다.'); - expect(container).toHaveTextContent('미추홀구 용현5동'); + expect(container).toHaveTextContent(product.title); + expect(container).toHaveTextContent(product.region); }); context('without product images', () => { diff --git a/src/pages/HomePage.test.jsx b/src/pages/HomePage.test.jsx index 1652b37..cd77959 100644 --- a/src/pages/HomePage.test.jsx +++ b/src/pages/HomePage.test.jsx @@ -8,6 +8,7 @@ import { render, fireEvent } from '@testing-library/react'; import HomePage from './HomePage'; import products from '../../fixtures/products'; +import product from '../../fixtures/product'; const mockPush = jest.fn(); @@ -55,9 +56,11 @@ describe('HomePage', () => { context('when click product', () => { it('occur handle event', () => { + const { title } = product; + const { getByText } = renderHomePage(); - fireEvent.click(getByText('크리넥스 KF-AD 소형 마스크 팝니다.')); + fireEvent.click(getByText(title)); expect(mockPush).toBeCalledWith('/products/1'); }); diff --git a/src/pages/ProductPage.test.jsx b/src/pages/ProductPage.test.jsx index 2f0d657..ee37cc3 100644 --- a/src/pages/ProductPage.test.jsx +++ b/src/pages/ProductPage.test.jsx @@ -7,43 +7,45 @@ import { useSelector, useDispatch } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import ProductPage from './ProductPage'; -import products from '../../fixtures/products'; +import product from '../../fixtures/product'; describe('ProductPage', () => { const dispatch = jest.fn(); + const { title, region, id } = product; beforeEach(() => { dispatch.mockClear(); useDispatch.mockImplementation(() => dispatch); useSelector.mockImplementation((selector) => selector({ productReducer: { - product: products[0], + product, }, })); }); context('with params props', () => { it('renders product', () => { - const params = { productId: 1 }; + const params = { productId: id }; + const { container } = render(( )); - expect(container).toHaveTextContent('크리넥스 KF-AD 소형 마스크 팝니다.'); - expect(container).toHaveTextContent('미추홀구 용현5동'); + expect(container).toHaveTextContent(title); + expect(container).toHaveTextContent(region); }); }); context('without params props', () => { it('renders product', () => { const { container } = render(( - + )); - expect(container).toHaveTextContent('크리넥스 KF-AD 소형 마스크 팝니다.'); - expect(container).toHaveTextContent('미추홀구 용현5동'); + expect(container).toHaveTextContent(title); + expect(container).toHaveTextContent(region); }); }); }); From 34063bd2d889a131cd21a75015e9714f2f4f8bf1 Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Wed, 6 Jan 2021 19:06:39 +0900 Subject: [PATCH 2/7] Refactor authSlice & api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * api의 로그인, 회원가입의 반환값을 user로 바꿈 * 이에 따라 authSlice를 refactoring 함. --- src/authSlice.js | 4 ++-- src/authSlice.test.js | 16 ++++++---------- src/services/api.js | 25 +++++++++---------------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/authSlice.js b/src/authSlice.js index af49b29..1da67ea 100644 --- a/src/authSlice.js +++ b/src/authSlice.js @@ -58,7 +58,7 @@ export function requestLogin({ loginFields }) { return async (dispatch) => { try { dispatch(setIsLoading(true)); - const { user } = await postLogin({ email, password }); + const user = await postLogin({ email, password }); const { displayName, uid } = user; dispatch(setUser({ email, displayName, uid })); @@ -101,7 +101,7 @@ export function requestSignup({ signupFields }) { return async (dispatch) => { try { dispatch(setIsLoading(true)); - const { user } = await postSignup({ email, password }); + const user = await postSignup({ email, password }); user.updateProfile({ displayName: userNickname }); const { displayName, uid } = user; diff --git a/src/authSlice.test.js b/src/authSlice.test.js index f3f0aa1..e4add60 100644 --- a/src/authSlice.test.js +++ b/src/authSlice.test.js @@ -87,8 +87,8 @@ describe('actions', () => { }); const loginFields = { - email: 'tester@example.com', - password: '1234abcd', + email: 'ghdrlfehd@example.com', + password: 'ghdrlfehd1234', }; describe('requestLogin', () => { @@ -97,9 +97,7 @@ describe('actions', () => { }); it('dispatches requestLogin action and returns user', async () => { - postLogin.mockImplementationOnce(() => ({ - user: logInUser, - })); + postLogin.mockImplementationOnce(() => logInUser); await store.dispatch(requestLogin({ loginFields })); @@ -172,7 +170,7 @@ describe('actions', () => { const signupFields = { firstName: '홍', lastName: '길동', - email: 'tester@example.com', + email: 'ghdrlfehd@example.com', password: '1234abcd', }; @@ -184,10 +182,8 @@ describe('actions', () => { it('returns user and change url path', async () => { const updateProfile = jest.fn(); postSignup.mockImplementationOnce(() => ({ - user: { - ...logInUser, - updateProfile, - }, + ...logInUser, + updateProfile, })); await store.dispatch(requestSignup({ signupFields })); diff --git a/src/services/api.js b/src/services/api.js index ddc386b..7b913b0 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; -import firebase from '../../plugin/firebase'; +import firebase from './firebase'; import { isEmpty } from '../utils'; export async function fetchProducts() { @@ -87,7 +87,7 @@ export async function deleteAllImages(productImages) { export async function postDeleteProduct({ product }) { const { id, productImages } = product; await firebase - .firestore().doc(`products/${id}`).delete(); + .firestore().collection('products').doc(id).delete(); if (isEmpty(productImages || [])) { return; @@ -98,35 +98,28 @@ export async function postDeleteProduct({ product }) { } export async function postLogin({ email, password }) { - const response = await firebase + const { user } = await firebase .auth() .signInWithEmailAndPassword(email, password); - return response; + return user; } export async function postGoogleSignIn() { - const response = await firebase - .auth() - .signInWithPopup( - new firebase.auth.GoogleAuthProvider(), - ); + const provider = new firebase.auth.GoogleAuthProvider(); + const response = await firebase.auth().signInWithPopup(provider); return response; } export async function postSignup({ email, password }) { - const response = await firebase + const { user } = await firebase .auth() .createUserWithEmailAndPassword(email, password); - return response; + return user; } export async function postLogout() { - const response = await firebase - .auth() - .signOut(); - - return response; + await firebase.auth().signOut(); } From 655c178758f75d1504656ad7ba980bf59c2174ba Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Wed, 6 Jan 2021 19:09:38 +0900 Subject: [PATCH 3/7] Move firebase.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * firebase 파일 이동 --- {plugin => src/services}/firebase.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {plugin => src/services}/firebase.js (100%) diff --git a/plugin/firebase.js b/src/services/firebase.js similarity index 100% rename from plugin/firebase.js rename to src/services/firebase.js From ab2388be482886c7d5c07e7841dbc972fcb1b158 Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Wed, 6 Jan 2021 19:10:06 +0900 Subject: [PATCH 4/7] Refactoring api test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * api 테스트 파일 리팩토링 --- src/services/__mocks__/firebase.js | 63 ++++++++++ src/services/api.test.js | 182 +++++++++++++++-------------- 2 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 src/services/__mocks__/firebase.js diff --git a/src/services/__mocks__/firebase.js b/src/services/__mocks__/firebase.js new file mode 100644 index 0000000..c90211c --- /dev/null +++ b/src/services/__mocks__/firebase.js @@ -0,0 +1,63 @@ +import products from '../../../fixtures/products'; + +const collections = { + products, +}; + +const firebase = { + auth: jest.fn(() => ({ + signInWithEmailAndPassword: jest.fn((email, password) => ({ + user: { + displayName: undefined, email, password, + }, + })), + createUserWithEmailAndPassword: jest.fn((email, password) => ({ + user: { + displayName: undefined, email, password, + }, + })), + signOut: jest.fn(), + })), + firestore: jest.fn(() => ({ + collection: jest.fn().mockImplementation((path) => ({ + get: jest.fn().mockResolvedValue({ + docs: collections[path].map((item) => ({ + id: item.id, + data: () => item, + })), + }), + doc: jest.fn().mockImplementation((docId) => ({ + get: jest.fn().mockResolvedValue({ + data: () => products.find(({ id }) => id === docId), + }), + update: jest.fn(), + delete: jest.fn(), + })), + add: jest.fn().mockImplementation((item) => item), + where: jest.fn().mockImplementation((fieldName, operator, value) => { + let result = null; + + if (operator === '==') { + result = ({ + get: jest.fn().mockResolvedValue({ + docs: collections[path] + .filter((doc) => doc.user.uid === value) + .map((doc) => ({ + id: doc.uid, + data: () => doc, + })), + }), + }); + } + return result; + }), + })), + })), + storage: jest.fn().mockImplementation(() => ({ + refFromURL: jest.fn().mockImplementation((url) => ({ + delete: jest.fn(), + })), + })), +}; + +export default firebase; diff --git a/src/services/api.test.js b/src/services/api.test.js index 3b805e5..9c27292 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -1,127 +1,131 @@ -import * as firebase from 'firebase'; - import { + fetchProduct, + fetchProducts, + fetchUserProducts, + postProduct, + postDeleteProduct, + postEditProduct, + deleteAllImages, postLogin, - postGoogleSignIn, postSignup, postLogout, - postProduct, } from './api'; -describe('firebase services', () => { - describe('postLogin', () => { - const mockPostLogin = ({ email, password }) => { - firebase.auth() - .signInWithEmailAndPassword = jest.fn().mockResolvedValue({ - email, password, - }); - }; - - const email = 'tester@example.com'; - const password = '123456'; - - beforeEach(() => { - mockPostLogin({ email, password }); - }); +import products, { userProducts } from '../../fixtures/products'; +import product from '../../fixtures/product'; +import { logInUser } from '../../fixtures/user'; + +jest.mock('./firebase.js'); - it('returns uid', async () => { - const user = await postLogin({ email, password }); +describe('api', () => { + describe('fetchProducts', () => { + it('returns products', async () => { + const data = await fetchProducts(); - expect(user).toEqual({ email, password }); + expect(data).toEqual(products); }); }); - describe('postGoogleSignIn', () => { - const mockGoogleSignIn = ({ user }) => { - firebase.auth() - .signInWithPopup = jest.fn().mockResolvedValue(user); - }; + describe('fetchProduct', () => { + it('returns product', async () => { + const productId = 1; - const user = { - displayName: 'tester', - uid: 'abc1234', - }; + const data = await fetchProduct(productId); - beforeEach(() => { - mockGoogleSignIn({ user }); + expect(data).toEqual( + products.find(({ id }) => id === productId), + ); }); + }); - it('returns uid', async () => { - const mockUser = await postGoogleSignIn(); + describe('fetchUserProducts', () => { + it('returns userProducts', async () => { + const user = logInUser; - expect(mockUser).toEqual({ - displayName: 'tester', - uid: 'abc1234', - }); + const data = await fetchUserProducts({ user }); + + expect(data).toEqual(userProducts); }); }); - describe('postLogout', () => { - const mockLogout = () => { - firebase.auth() - .signOut = jest.fn().mockResolvedValue(true); - }; + describe('postProduct', () => { + it('return post id', async () => { + const data = await postProduct(product); - beforeEach(() => { - mockLogout(); + expect(data).toEqual(product.id); }); + }); - it('returns promise', async () => { - const logout = await postLogout(); + describe('postEditProduct', () => { + it('request product post edit', async () => { + const productId = 1; + const editedProduct = { + ...product, + title: '가전제품 팜', + price: 400000, + }; - expect(logout).toBe(true); + await postEditProduct({ productId, editedProduct }); }); }); - describe('postSignup', () => { - const mockFirebaseSignup = ({ email, password }) => { - firebase.auth() - .createUserWithEmailAndPassword = jest.fn().mockResolvedValue({ - displayName: '', - email, - password, - uid: '', - }); - }; - - const email = 'tester@example.com'; - const password = '123456'; - - beforeEach(() => { - mockFirebaseSignup({ email, password }); + describe('postDeleteProduct', () => { + context('productImages is not empty', () => { + it('request delete all images and product post delete', async () => { + const { productImages } = product; + + await postDeleteProduct({ product }); + + const deleteUrls = productImages.map(({ imageUrl }) => imageUrl); + + await deleteAllImages(deleteUrls); + }); + }); + + context('productImages is empty', () => { + it('request only product pos delete', async () => { + const emptyImagesProductPost = { + ...product, + productImages: [], + }; + await postDeleteProduct({ product: emptyImagesProductPost }); + }); }); + }); + + describe('postLogin', () => { + it('returns user', async () => { + const email = 'ghdrlfehd@test.com'; + const password = '123456'; - it('returns new account', async () => { - const newUser = await postSignup({ email, password }); + const data = await postLogin({ email, password }); - expect(newUser.email).toBe(email); + expect(data).toEqual({ + displayName: undefined, + email, + password, + }); }); }); - describe('postProduct', () => { - const add = jest.fn((product) => product); - const collection = jest.spyOn( - firebase.firestore(), 'collection', - ).mockReturnValue({ add }); - - it('post new product', async () => { - const newProduct = { - title: 'test title', - description: 'test description', - productImages: [], - createdAt: Date.now(), - user: { - uid: 'test1234', - displayName: '홍 길동', - email: 'tester@example.com', - }, - }; + describe('postSignup', () => { + it('returns sign up user', async () => { + const email = 'ghdrlfehd@test.com'; + const password = '123456'; - await postProduct(newProduct); + const data = await postSignup({ email, password }); - expect(collection).toHaveBeenCalledWith('products'); + expect(data).toEqual({ + displayName: undefined, + email, + password, + }); + }); + }); - expect(add).toHaveBeenCalledWith(newProduct); + describe('postLogout', () => { + it('request logout', async () => { + await postLogout(); }); }); }); From b4ea93335118e6df03a90c4d3d7b1459160fcc7a Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Wed, 6 Jan 2021 20:59:42 +0900 Subject: [PATCH 5/7] Refactor Google login test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 구글 로그인에 대한 테스트를 작성하고 커버리지를 올린다. --- src/authSlice.js | 2 +- src/authSlice.test.js | 4 +-- src/services/__mocks__/firebase.js | 16 ++++++++++-- src/services/api.js | 15 +++++------ src/services/api.test.js | 42 +++++++++++++++++++++++++++--- src/services/firebase.js | 3 +++ 6 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/authSlice.js b/src/authSlice.js index 1da67ea..63f9f6b 100644 --- a/src/authSlice.js +++ b/src/authSlice.js @@ -77,7 +77,7 @@ export function requestGoogleSignIn() { return async (dispatch) => { try { dispatch(setIsLoading(true)); - const { user } = await postGoogleSignIn(); + const user = await postGoogleSignIn(); const { email, displayName, uid } = user; dispatch(setUser({ email, displayName, uid })); diff --git a/src/authSlice.test.js b/src/authSlice.test.js index e4add60..7f1fb42 100644 --- a/src/authSlice.test.js +++ b/src/authSlice.test.js @@ -133,9 +133,7 @@ describe('actions', () => { context('when request success', () => { it('returns user and change url path', async () => { - postGoogleSignIn.mockImplementationOnce(() => ({ - user: logInUser, - })); + postGoogleSignIn.mockImplementationOnce(() => logInUser); await store.dispatch(requestGoogleSignIn()); diff --git a/src/services/__mocks__/firebase.js b/src/services/__mocks__/firebase.js index c90211c..582f413 100644 --- a/src/services/__mocks__/firebase.js +++ b/src/services/__mocks__/firebase.js @@ -1,9 +1,14 @@ import products from '../../../fixtures/products'; +import { logInUser } from '../../../fixtures/user'; const collections = { products, }; +const googleAuthLogin = jest.fn().mockImplementation(() => ({ + user: logInUser, +})); + const firebase = { auth: jest.fn(() => ({ signInWithEmailAndPassword: jest.fn((email, password) => ({ @@ -34,7 +39,7 @@ const firebase = { delete: jest.fn(), })), add: jest.fn().mockImplementation((item) => item), - where: jest.fn().mockImplementation((fieldName, operator, value) => { + where: jest.fn().mockImplementation((_, operator, value) => { let result = null; if (operator === '==') { @@ -54,10 +59,17 @@ const firebase = { })), })), storage: jest.fn().mockImplementation(() => ({ - refFromURL: jest.fn().mockImplementation((url) => ({ + refFromURL: jest.fn().mockImplementation(() => ({ delete: jest.fn(), })), + ref: jest.fn(() => ({ + child: jest.fn(() => ({ + put: jest.fn(), + getDownloadURL: jest.fn(() => 'MOCK_IMAGE_URL'), + })), + })), })), }; export default firebase; +export { googleAuthLogin }; diff --git a/src/services/api.js b/src/services/api.js index 7b913b0..ed592dd 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; -import firebase from './firebase'; +import firebase, { googleAuthLogin } from './firebase'; import { isEmpty } from '../utils'; export async function fetchProducts() { @@ -43,10 +43,10 @@ export async function fetchUserProducts({ user }) { export async function uploadProductImages({ files }) { async function uploadProductImage(file) { - const uploadTask = firebase.storage() + const reference = firebase.storage() .ref().child(`${file.name}${uuidv4()}`); - const response = await uploadTask.put(file); - const imageUrl = await response.ref.getDownloadURL(); + await reference.put(file); + const imageUrl = await reference.getDownloadURL(); return imageUrl; } @@ -89,7 +89,7 @@ export async function postDeleteProduct({ product }) { await firebase .firestore().collection('products').doc(id).delete(); - if (isEmpty(productImages || [])) { + if (isEmpty(productImages)) { return; } @@ -106,10 +106,9 @@ export async function postLogin({ email, password }) { } export async function postGoogleSignIn() { - const provider = new firebase.auth.GoogleAuthProvider(); - const response = await firebase.auth().signInWithPopup(provider); + const { user } = await googleAuthLogin(); - return response; + return user; } export async function postSignup({ email, password }) { diff --git a/src/services/api.test.js b/src/services/api.test.js index 9c27292..970419e 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -9,6 +9,8 @@ import { postLogin, postSignup, postLogout, + uploadProductImages, + postGoogleSignIn, } from './api'; import products, { userProducts } from '../../fixtures/products'; @@ -56,6 +58,31 @@ describe('api', () => { }); }); + describe('uploadProductImages', () => { + it('returns productImages', async () => { + const files = [ + new File(['file'], 'productImage1.png', { + type: 'application/json', + }), + new File(['file'], 'productImage2.png', { + type: 'application/json', + }), + new File(['file'], 'productImage3.png', { + type: 'application/json', + }), + ]; + + const data = await uploadProductImages({ files }); + + const productImages = files.map((file, _) => ({ + name: file.name, + imageUrl: 'MOCK_IMAGE_URL', + })); + + expect(data).toEqual(productImages); + }); + }); + describe('postEditProduct', () => { it('request product post edit', async () => { const productId = 1; @@ -83,12 +110,13 @@ describe('api', () => { }); context('productImages is empty', () => { - it('request only product pos delete', async () => { - const emptyImagesProductPost = { + it('request only post delete', async () => { + const emptyImagesInPost = { ...product, productImages: [], }; - await postDeleteProduct({ product: emptyImagesProductPost }); + + await postDeleteProduct({ product: emptyImagesInPost }); }); }); }); @@ -108,6 +136,14 @@ describe('api', () => { }); }); + describe('postGoogleSignin', () => { + it('returns user', async () => { + const data = await postGoogleSignIn(); + + expect(data).toEqual(logInUser); + }); + }); + describe('postSignup', () => { it('returns sign up user', async () => { const email = 'ghdrlfehd@test.com'; diff --git a/src/services/firebase.js b/src/services/firebase.js index 514156d..6a495ee 100644 --- a/src/services/firebase.js +++ b/src/services/firebase.js @@ -15,4 +15,7 @@ const firebaseConfig = { firebase.initializeApp(firebaseConfig); +const googleAuthProvider = new firebase.auth.GoogleAuthProvider(); +export const googleAuthLogin = () => firebase.auth().signInWithPopup(googleAuthProvider); + export default firebase; From 34abbf60bbeec9befa9cd4c5194e9dc1705c7e65 Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Thu, 7 Jan 2021 00:48:17 +0900 Subject: [PATCH 6/7] Fix lint error * lint error fix --- src/components/presentational/ProductDetail.test.jsx | 8 ++++---- src/services/api.test.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/presentational/ProductDetail.test.jsx b/src/components/presentational/ProductDetail.test.jsx index ae05076..9d0790d 100644 --- a/src/components/presentational/ProductDetail.test.jsx +++ b/src/components/presentational/ProductDetail.test.jsx @@ -5,7 +5,6 @@ import { render, fireEvent } from '@testing-library/react'; import ProductDetail from './ProductDetail'; import products from '../../../fixtures/products'; -import product from '../../../fixtures/product'; describe('ProductDetail', () => { function renderProductDetail({ product }) { @@ -17,10 +16,11 @@ describe('ProductDetail', () => { } it('renders product detail', () => { - const { container } = renderProductDetail({ product }); + const { title, region } = products[0]; + const { container } = renderProductDetail({ product: products[0] }); - expect(container).toHaveTextContent(product.title); - expect(container).toHaveTextContent(product.region); + expect(container).toHaveTextContent(title); + expect(container).toHaveTextContent(region); }); context('without product images', () => { diff --git a/src/services/api.test.js b/src/services/api.test.js index 970419e..6de8c5c 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -74,7 +74,7 @@ describe('api', () => { const data = await uploadProductImages({ files }); - const productImages = files.map((file, _) => ({ + const productImages = files.map((file) => ({ name: file.name, imageUrl: 'MOCK_IMAGE_URL', })); From dd915ff9026fec770ae643128fa9f63f1d2ed5d3 Mon Sep 17 00:00:00 2001 From: Kwakcena Date: Thu, 14 Jan 2021 18:58:48 +0900 Subject: [PATCH 7/7] Refactor] authSlice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * authSlice에서 requestGoogleSignIn 함수에 try catch를 제거함 --- src/authSlice.js | 24 +++++++++---------- src/authSlice.test.js | 37 +++++++----------------------- src/services/__mocks__/firebase.js | 7 ++---- src/services/api.js | 8 +------ src/services/api.test.js | 9 -------- src/services/firebase.js | 4 ++-- 6 files changed, 24 insertions(+), 65 deletions(-) diff --git a/src/authSlice.js b/src/authSlice.js index 63f9f6b..25809f9 100644 --- a/src/authSlice.js +++ b/src/authSlice.js @@ -6,9 +6,12 @@ import { saveItem, deleteItem } from './services/storage'; import { setIsLoading } from './commonSlice'; +import { + googleAuthLogin, +} from './services/firebase'; + import { postLogin, - postGoogleSignIn, postSignup, postLogout, } from './services/api'; @@ -75,20 +78,15 @@ export function requestLogin({ loginFields }) { export function requestGoogleSignIn() { return async (dispatch) => { - try { - dispatch(setIsLoading(true)); - const user = await postGoogleSignIn(); - const { email, displayName, uid } = user; + dispatch(setIsLoading(true)); + const { user } = await googleAuthLogin(); + const { email, displayName, uid } = user; - dispatch(setUser({ email, displayName, uid })); - saveItem('user', { email, displayName, uid }); - dispatch(setIsLoading(false)); + dispatch(setUser({ email, displayName, uid })); + saveItem('user', { email, displayName, uid }); + dispatch(setIsLoading(false)); - dispatch(push('/')); - } catch (error) { - dispatch(setError(error.message)); - dispatch(setIsLoading(false)); - } + dispatch(push('/')); }; } diff --git a/src/authSlice.test.js b/src/authSlice.test.js index 7f1fb42..f9b04d0 100644 --- a/src/authSlice.test.js +++ b/src/authSlice.test.js @@ -12,7 +12,6 @@ import authReducer, { } from './authSlice'; import { - postGoogleSignIn, postLogin, postSignup, } from './services/api'; @@ -24,6 +23,7 @@ const middlewares = [...getDefaultMiddleware()]; const mockStore = configureStore(middlewares); jest.mock('./services/api'); +jest.mock('./services/firebase'); jest.mock('connected-react-router'); describe('reducer', () => { @@ -131,36 +131,15 @@ describe('actions', () => { store = mockStore({}); }); - context('when request success', () => { - it('returns user and change url path', async () => { - postGoogleSignIn.mockImplementationOnce(() => logInUser); - - await store.dispatch(requestGoogleSignIn()); + it('returns user and change url path', async () => { + const actions = store.getActions(); - const actions = store.getActions(); + await store.dispatch(requestGoogleSignIn()); - expect(actions[0]).toEqual(setIsLoading(true)); - expect(actions[1]).toEqual(setUser(logInUser)); - expect(actions[2]).toEqual(setIsLoading(false)); - expect(actions[3]).toEqual(push('/')); - }); - }); - - context('when request fail', () => { - it('returns an error', async () => { - postGoogleSignIn.mockImplementationOnce( - () => Promise.reject( - new Error('something bad happened'), - ), - ); - - try { - await store.dispatch(requestGoogleSignIn()); - } catch { - const actions = store.getActions(); - expect(actions[0].payload.error).toEqual('Something bad happened'); - } - }); + expect(actions[0]).toEqual(setIsLoading(true)); + expect(actions[1]).toEqual(setUser(logInUser)); + expect(actions[2]).toEqual(setIsLoading(false)); + expect(actions[3]).toEqual(push('/')); }); }); diff --git a/src/services/__mocks__/firebase.js b/src/services/__mocks__/firebase.js index 582f413..b80a0c4 100644 --- a/src/services/__mocks__/firebase.js +++ b/src/services/__mocks__/firebase.js @@ -5,9 +5,7 @@ const collections = { products, }; -const googleAuthLogin = jest.fn().mockImplementation(() => ({ - user: logInUser, -})); +const googleAuthLogin = jest.fn().mockImplementation(() => Promise.resolve({ user: logInUser })); const firebase = { auth: jest.fn(() => ({ @@ -71,5 +69,4 @@ const firebase = { })), }; -export default firebase; -export { googleAuthLogin }; +export { firebase, googleAuthLogin }; diff --git a/src/services/api.js b/src/services/api.js index ed592dd..9a2c8b0 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; -import firebase, { googleAuthLogin } from './firebase'; +import { firebase } from './firebase'; import { isEmpty } from '../utils'; export async function fetchProducts() { @@ -105,12 +105,6 @@ export async function postLogin({ email, password }) { return user; } -export async function postGoogleSignIn() { - const { user } = await googleAuthLogin(); - - return user; -} - export async function postSignup({ email, password }) { const { user } = await firebase .auth() diff --git a/src/services/api.test.js b/src/services/api.test.js index 6de8c5c..29eff2b 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -10,7 +10,6 @@ import { postSignup, postLogout, uploadProductImages, - postGoogleSignIn, } from './api'; import products, { userProducts } from '../../fixtures/products'; @@ -136,14 +135,6 @@ describe('api', () => { }); }); - describe('postGoogleSignin', () => { - it('returns user', async () => { - const data = await postGoogleSignIn(); - - expect(data).toEqual(logInUser); - }); - }); - describe('postSignup', () => { it('returns sign up user', async () => { const email = 'ghdrlfehd@test.com'; diff --git a/src/services/firebase.js b/src/services/firebase.js index 6a495ee..9a17605 100644 --- a/src/services/firebase.js +++ b/src/services/firebase.js @@ -16,6 +16,6 @@ const firebaseConfig = { firebase.initializeApp(firebaseConfig); const googleAuthProvider = new firebase.auth.GoogleAuthProvider(); -export const googleAuthLogin = () => firebase.auth().signInWithPopup(googleAuthProvider); +const googleAuthLogin = () => firebase.auth().signInWithPopup(googleAuthProvider); -export default firebase; +export { firebase, googleAuthLogin };