사전 지식
블록킹은 순서대로 실행된다.
논 블로킹은 순서대로 안 실행된다.
프로그래밍에 랜덤이라는건 없다.
논블로킹은 순서대로 실행되지 않는데
그것마저도 규칙이있다.
=> 순서대로 실행되지 않는데 그것마저도 규칙이있는것.
그게 "이벤트 루프다"
동기가 순서대로 실행되는건 '실행컨텍스트'.
프로토타입
실행컨텍스트 안에 this 랑 scope 다.
zshrc 에서 code 자꾸 안먹힐때 ㅠ..
vim ~/.zshrc <- 터미널에서 입력
파일 제일 아래에 다음 코드를 추가
code () { VSCODE_CWD="$PWD" open -n -b "com.microsoft.VSCode" --args $* ;}
그리고 변경된 .zshrc 파일을 한번 실행 시킨다.
-> $ source ~/.zshrc
끝.
React Setup
step1. Make react new Project
$ npx create-react-app [project] template=typescript
[Optional] What is Tail Wind?
super super Ui frame work
try : $ npm install tailwindcss
market: tailwind => Search => Tailwind CSS InteliSense down!
When you Expect Autocomplete css word !. CSS InteliSenese is power plugin !
important => However, tail wind is available from node version 12
detail documentation search url => [https://tailwindcss.com/docs/installation]
what is apollo client ?
Apollo Client?
자바 스크립트를 위한 상태 관리 라이브러리다.
GraphqlQL쿼리를 작성하면 ApolloClient는 데이터를 가져오고 캐싱을 해주고 UI를 업로드 해 준다.
GraphQL의 모든 요청은 POST로 하기 때문에 브라우저에서 캐싱이 되지 않습니다. 물론 캐싱뿐만이 아니라 많은 기능을 제공하기 때문에 사용한다.
장점
개인적으로는 데이터 관리 & 유지보수가 뛰어납니다. 모든 상태를 전역으로 관리할 필요도 없으며
GraphQL 쿼리 형태 그대로 데이터를 가져올 수 있습니다.
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';
const GET_DOGS = gql`
{
dogs {
id
breed
}
}
`;
GraphQL 플레이 그라운드와 같은 형식으로 작성하면 쉽게 가져올 수 있습니다.
const { loading, error, data } = useQuery(GET_DOGS);
로딩 에러 데이터 등등 관리를 쉽게 할 수 있습니다.
data GraphQL 쿼리 결과가 포함 된 객체입니다. 기본값은 undefined입니다.
loading 로딩 상태를 나타냅니다. 불리언 값으로 나타납니다.
error graphQLErrors및 networkError속성 의 런타임 오류를 받습니다.
variables 쿼리가 호출 된 변수를 포함하는 객체
install tailwind post frefix
post frefix 가 뭐냐면. border-radius 같은 경우는 fire fox 에서 [ 특정 브라우저에서 사용 할 수 없는데]
이럴경우 -moz-border-radius 처럼 앞에 -moz 라는 접두어를 붙여야 작동한다.
이러한 일을 자동으로 처리해 주는 녀석이 tailwind 에서 post frefix 이다.
설치 방법은 아래와 같다.
$ npm install tailwindcss@latest postcss@latest autoprefixer@latest
설치를 하고나서. root 디렉토리에 file 을 하나 추가해 주자.
File name 은 postcss.config.js 라고 해주고,
내용은 아래와 같다.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
Tailwind 를 확장할땐. (커스터마이즈)
tailwindcssnpm 패키지 를 설치할 때 포함 된 Tailwind CLI 유틸리티를 사용하여
프로젝트에 대한 구성 파일을 생성 해줘야 한다.
$ npx tailwindcss init
내용은 자동으로 채워지고, 이렇게 해두면, tailWind 를 커스터마이징 할 수 있다.
정리하면 , tailwind 를 커스터 마이즈 하기 위해 Tailwind config 파일이 필요하고,
tailwind 를 일반 css 파일로 *빌드 하기 위해 postcss config 파일 이 필요하다.
이렇게 해두면 쉽게 커스터마이징 할 수 있고, 방법은
다음 url 에서 자세히 살펴볼 수 있다.
url : https://tailwindcss.com/docs/extracting-components
test 하기 위해 . root > src 폴더에 css 파일을 하나 만들어 주자.
=> tailwind.css
그런다음 공식 문서에 있는 다음 코드를 작성해 준다.
@tailwind base;
@tailwind components;
.btn { // 사용자 정의
@apply px-4 py-2 bg-blue-600 text-white rounded;
}
@tailwind utilities;
위의 코드들은 post css 가 이 파일을 보고 Tailwind 가 갖고 있는 모든 클래스 이름으로
바꾸어 준다 + tailwind config 파일을 들여다 보고 새 클래스 이름이 있다면 그것도 추가해준다.
아직 끝난게 아니다. 사용자 정의용으로 만든 tailwind.css 파일을 => tailwind 에 적용하는 것이 남았다.
방법은 다음과 같다.
1. package.json 에 script 부분을 보면 start, build, test, eject 부분에 tailwind 용 build 를 추가해
줘야 한다. 추가하면 해당 명령어로 인해 tailwind
[ 이부분은 자유 네이밍 이 가능 ] 그뒤에 : "tailvind build 인풋파일경로 를 -o 뒤엔 아웃풋 파일 경로를"
"tailwind:build": "tailwind build ./src/styles/tailwind.css -o ./src/styles/styles.css",
npm run tailwind:build
> nuber-eats-clone-front@0.1.0 tailwind:build /Users/herings/project/back-end/nuber-eats-clone-front
> tailwind build ./src/styles/tailwind.css -o ./src/styles/styles.css
tailwindcss 2.0.2
🚀 Building: src/styles/tailwind.css
✅ Finished in 3.88 s
📦 Size: 3.75MB
💾 Saved to src/styles/styles.css
실행하고 나선 위와 같은 완료 문구를 볼 수 있는데 size 가 꽤 크다.
그러나 쓰이지 않는 class 를 줄일 수 있기 떄문에 걱정하지 않아도 된다.
build 후엔 본인이 만들어 놓은 styles 폴더 하위에 tailwind.css 파일 말고도 styles.css 파일이 생성된것을
확인 할 수 있다. 또 .btn 클래스가 tailwind 에 맞게끔 소스가 바뀌었다.
이게 커스텀후 tailwind 에게 맞는 소스로 바꾸는 작업이다.
이제 필요한 곳에서 해당 css 를 import 하면된다.
그러나 변경 할때마다 build 를 해줘야 하는 귀찮은점이 있다. 이렇땐 package.json 에서 작업을 해줘야 한다.
script 부분의 start 를 다음과 같이 변경해 준다.
"start": "npm run tailwind:build && react-scripts start",
이렇게 하면 변경이 이루어질 때마다 build 가 이뤄진다는 걸 보장 할 수있다.
apollo ?
Apollo Client는 클라이언트 애플리케이션에서 GraphQL과의 데이터 교환을 도우며,
특별히 리액트에서 사용하는 라이브러리를 리액트 아폴로(React Apollo)라고 부른다.
설치부터 하자.
$ npm install @apollo/client graphql
위의 @apollo/client:이 단일 패키지에는 Apollo Client를 설정하는 데 필요한 거의 모든 것이 포함되어 있다.
여기에는 메모리 내 캐시, 로컬 상태 관리, 오류 처리 및 React 기반 뷰 레이어가 포함된다.
graphql:이 패키지는 GraphQL 쿼리 구문 분석을위한 로직을 제공한다.
먼저 클라이언트 파일을 손봐야 한다.
공식 문서에 있는 아래 코드에서 uri 를 백엔드 uri 로 바꿔주어야 한다.
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost',
cache: new InMemoryCache()
});
isolation error ?
-isolation error 에 빠지면 재시작이 답이다.
Local state ?
local state 는 server 에는 없지만 application 에는 있기를 바라는 그런 state 다.
예를 들어 로그인, 로그아웃 같은 경우.
apollo.ts 에 가면 client 를 관장하는 곳이 있다.
이곳으로 와서 cache 부분을 손봐야 한다.
cache: new InMemoryCache() 안에 다음과 같이 꾸며줘 보도록 한다.
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
isLoggedIn: {
read() {
return false;
}
}
}
}
}
})
이렇게 해놓고 App.tsx 는 다음과 같이 바꿔 주도록 한다.
import { gql, useQuery } from '@apollo/client';
import React from 'react';
import { LoggedOutRouter } from './routers/logged-out-router';
const IS_LOGGED_IN = gql`
query isLoggedIn {
isLoggedIn @client
}
`;
function App() {
const { data } = useQuery(IS_LOGGED_IN);
console.log(data);
return <LoggedOutRouter />;
}
export default App;
이게 local state 에 접근하는 방법이다.
Router ?
Router 에는 { hashRouter , BrowserRouter } 두가지가 있다.
hashRouter/#
BrowserRouter/
여기선 멋진 BrowserRouter 을 사용한다.
mutation 을 보호해주는 역할을 한다.
(검증된.)
apollo tooling 은 back-end 에서 mutations, query responses, input type 을
전부 다 typescript 정의를 자동으로 생성해 준다.
이말은 내가 mutation 을 보낼때 back-end response 의 타입을 알 수 있다는 이야기다.
$ apollo client:codegen mytypes.d.ts --target=typescript 실행....
지린다....
SAGE
$ apollo client:codegen [OUTPUT]
ARGUMENTS
OUTPUT
Directory to which generated files will be written.
- For TypeScript/Flow generators, this specifies a directory relative to each source file by default.
- For TypeScript/Flow generators with the "outputFlat" flag is set, and for the Swift generator, this specifies
a file or directory (absolute or relative to the current working directory) to which:
- a file will be written for each query (if "output" is a directory)
- all generated types will be written
- For all other types, this defines a file (absolute or relative to the current working directory) to which all
generated types are written.
OPTIONS
-c, --config=config Path to your Apollo config file
-g, --graph=graph The ID for the graph in Apollo to operate client commands with.
Overrides config file if set.
-v, --variant=variant The variant of the graph in Apollo to associate this client to
--[no-]addTypename [default: true] Automatically add __typename to your queries, can be
unset with --no-addTypename
--clientName=clientName Name of the client that the queries will be attached to
--clientReferenceId=clientReferenceId Reference id for the client which will match ids from client traces,
will use clientName if not provided
--clientVersion=clientVersion The version of the client that the queries will be attached to
--customScalarsPrefix=customScalarsPrefix Include a prefix when using provided types for custom scalars
--endpoint=endpoint The URL for the CLI use to introspect your service
--excludes=excludes Glob of files to exclude for GraphQL operations. Caveat: this doesn't
currently work in watch mode
--globalTypesFile=globalTypesFile By default, TypeScript will put a file named "globalTypes.ts" inside
the "output" directory. Set "globalTypesFile" to specify a different
path. Alternatively, set "tsFileExtension" to modify the extension of
the file, for example "d.ts" will output "globalTypes.d.ts"
--header=header Additional header to send during introspection. May be used multiple
times to add multiple headers. NOTE: The `--endpoint` flag is REQUIRED
if using the `--header` flag.
--includes=includes Glob of files to search for GraphQL operations. This should be used to
find queries *and* any client schema extensions
--key=key The API key to use for authentication to Apollo
--localSchemaFile=localSchemaFile Path to one or more local GraphQL schema file(s), as introspection
result or SDL. Supports comma-separated list of paths (ex.
`--localSchemaFile=schema.graphql,extensions.graphql`)
--mergeInFieldsFromFragmentSpreads Merge fragment fields onto its enclosing type
--namespace=namespace The namespace to emit generated code into.
--omitDeprecatedEnumCases Omit deprecated enum cases from generated code [Swift only]
--only=only Parse all input files, but only output generated code for the specified
file [Swift only]
--operationIdsPath=operationIdsPath Path to an operation id JSON map file. If specified, also stores the
operation ids (hashes) as properties on operation types [currently
Swift-only]
--outputFlat By default, TypeScript/Flow will put each generated file in a directory
next to its source file using the value of the "output" as the
directory name. Set "outputFlat" to put all generated files in the
directory relative to the current working directory defined by
"output".
--passthroughCustomScalars Use your own types for custom scalars
--queries=queries Deprecated in favor of the includes flag
--suppressSwiftMultilineStringLiterals Prevents operations from being rendered as multiline strings [Swift
only]
--tagName=tagName Name of the template literal tag used to identify template literals
containing GraphQL queries in Javascript/Typescript code
--target=target (required) Type of code generator to use (swift | typescript | flow |
scala | json | json-modern (exposes raw json types))
--tsFileExtension=tsFileExtension By default, TypeScript will output "ts" files. Set "tsFileExtension" to
specify a different file extension, for example "d.ts"
--useFlowExactObjects Use Flow exact objects for generated types [flow only]
--useFlowReadOnlyTypes Use read only types for generated types [flow only]. **Deprecated in
favor of `useReadOnlyTypes`.**
--useReadOnlyTypes Use read only types for generated types [flow | typescript]
--watch Watch for file changes and reload codegen
typescript
$ apollo client:codegen src/__api_type__ --target=typescript --outputFlat
=> 각 파일마다 하나씩 src ( 꼭 하위여야 한다. ) 밑의 [자유선언 폴더 ] __api_types__ 같이.
추가적으로 자꾸 __api_type__ 에 기존에 있던 코드들이 남아있어 지우고 깔끔하게 다시 apollo:codegen 을 해줘야
하는 번거로움이 있는데 , 이를 해소하기 위해 아래와 같은 코드를 심어 줘야 한다.
"apollo:codegen": "rm -rf src/__api_type__ &&
apollo client:codegen src/__api_type__ --target=typescript --outputFlat",
rm -rf 이 명령어는 리눅스나, mac 에서만 작동한다. 따라서 rimraf 를 설치해줘서 windows 에서도 동일한 명령을 수행 할 수 있게
바꿔주도록 하자.
* rimraf 는 패키지 인데 많은 사람들이 사용하지 않는다.
$ npm i rimraf
설치를 마치고 아래와 같은 코드를 package.json 에서 기존의 것을 지워주고 다시 작성해 주도록 한다.
"apollo:codegen" :rimraf src/__api_types__ &&
apollo client:codegen src/__api_type__ --target=typescript --outputFlat
codegen 이 일부 추가 되었으니 start 명령어도 손봐 주도록 한다.
"start": "npm run apollo:codegen & npm run tailwind:build && react-scripts start",
tail wind 시그니처 컬러 적용방법
tailwind.config.js 에서 제일 상단에 아래와 같은 코드를 삽입.
const colors = require('tailwindcss/colors')
하위에 theme 아래 주석으로 중요하다는 부분을 눈여겨 보자.
module.exports = {
purge: [],
darkMode: false,
theme: {
extend: {
colors: {
lime: colors.lime
}
},
},
variants: {
extend: {},
},
plugins: [],
}
what is react-helmet
npm i react-helmet
title 을 바꿔주는 라이브러리다. 기본 title 은 reactApp 이다.
localstorage
토큰을 애플리케이션 단에서 저장하고 싶을때, 혹은 애플리케이션 내에 저장 하고싶은 상태는
apollo.ts 가 관장한다.
Switch ?
Switch 가 있으면 한번에 route 하나만 render 하라고 알려준다.
rem ?
em ? rem ?
em 은 예를 들면 document 에 있는 font 의 사이즈다.
document 에 있는 font 가 16px 이라고 가정하고 ,
margin top 을 1em 으로 가정해주면 margin-top 은 16px 이 된다.
f-t: 16px
m-t: 1em === 16px
f-t: 50px 이면
m-t: 2em 이다.
이게 em 이다. em 은 element 의 사이즈에 좌지우지 된다.
tailwind css 가 padding 과 size 를 이용하는 방법은
rem 이다.
rem 은 root em 이란 뜻이다.
rem 은 body 의 font 사이즈를 가져와서 number 만큼 곱하고,
em 은 가장 가까이 정의되어있는 font 사이즈를 가져와서 number 만큼 곱한다.
< body : f-t :5px
< f-t : 50px
rem => 2rem => 10px;
em => 2em => 100px;
react font-awesome ?
아이콘 무료 버전이 있고 pro (유료) 버전이 있으니 잘 알아보고 적용하자.
npm i @fortawesome/fontawesome-svg-core
@fortawesome/free-solid-svg-icons
@fortawesome/react-fontawesome
일반적인 js 에서 특정 url 값 뜯어오기
window.location.href.split("code=")
솔직히 위 만한게 없다.
그러나 우리는 react-router 를 쓰기 때문에
useLocation 을 사용 하면 더 간단하게 뜯어올 수 있다.
cache 에 write 하는 방법
client.writeFragment({
id: `User${useData.me.id}`,
fragment: gql` // 그러면 fragment 를 write 할 수 있다. fragment 는 큰 type 의 일부분이다.
fragment VerifiedUser on User { // 예를들어 여기서는 type 이 user 다. 나는 이 fragment 를 cache 로 write 한다.
verified // 이것만 바꾼다. 그리고 여기서 선언한 data 는
}
`,
data: {
verified: true,
}
})
cache
apollo client ?
const client = useApolloClient();
우리가 원하는 클라이언트를 줄 수 있다.
application 에서 일어나는 모든 update 를 최적화 하는방법 back-end api 에 의존 X
1. 직접 cache 를 업데이트 하는 방법.
2. 직접 query 를 refresh 하는 방법.
결국 이게 무슨 말이냐면, mutation 이 일어나면 내가 직접 cache 를 update 할꺼란 이야기.
=> object 의 id를 갖고, fragment를 write하고 , data 를 send 한다.
const onCompleted = async (data: editProfile) => {
const { editProfile: { ok } } = data;
if (ok && userData) {
const { me: { email: prevEmail, id } } = userData;
const { email: newEmail } = getValues();
if (prevEmail !== newEmail) {
client.writeFragment({
id: `User:${id}`,
fragment: gql`
fragment EditedUser on User {
verified
email
}
`,
data: {
verified: false,
email: newEmail,
}
});
}
}
};
refetch
refetch 는 function 인데 이걸 call 하면 query 를 다시 fetch 해준다.
그렇게 하면 cache 가 자동적으로 update 될것이고, apollo 는 자동으로 cache 를 업데이트 해준다.
tailwind css
아무것도 붙어 있지 않은경우 엔 모바일에 적용될것이고,
md: 같은게 붙어있다면 medium 사이즈나 그보다 큰 화면에 적용될것이다.
이뉴 는 form 을 빠르게 작성할 수 있기 떄문이다.
일반적인 form 은 보통 form submit 에 validation 에 여러것들이 붙어있다보니
작성이 불가피하게 코딩을 많이 해야한다.
그러나 react-form 을 쓰면 이러한 것들을 최소화 시킬 수 있다.
다른 페이지로 redirect 하기 위해선 history 가 필요하다.
react-router 에서 useHistory() 를 제공한다.
history push 는 object 를 제공한다.
useLocation vs useHistory
useLocation 은 우리가 어딘지를 알 수 있고,
useHistory 는 우리가 어디로 갈 수 있다.
history push ?
history.push 에 1번 , 2 번의 차이점
history.push({
pathname: "/search",
search: `?term=${searchTerm}`, // [1]
state: { // [2]
searchTerm
}
});
1번의 경우엔 url 에 페이지가 표현 하고 자 하는 내용이 담겨있다.
2번의 경우엔 url 에 표현하고자 하는 내용이 숨김처리가 된다 + 브라우저가 Route 의 state 를 기억해준다.
Route 의 state 가 브라우저 메모리에 저장되기 때문이다.
적절히 숨겨야 할 페이지엔 숨기고 그렇지 않을경우엔 그대로 노출시켜주면 공유가 가능하니
적절히 사용하면 좋을것같다.
orm + gql 에서 Search 그리고 Lazy Query
조건에 따라 달라지는 query 를 실행해야 한다.
우리는 /search? 라는 url 에 query 를 보내야 한다.
search 관련 gql 에는 input 으로 query 와 page 가 필요하다.
그에 따라 시간이 다소 걸리고, (string 으로 검색하기 때문) + 사용자가 url의 일부를 지울수도 있기 때문에
query 가 실패하고 gql 에러를 터트리게 되는데 lazy Query 를 알아야 한다.
lazy Query 는 바로 실행되지 않는것이다. 내가 불러야 한다.
직접 call 하기 위해 "저기요 실행하세요" 라고 불러줘야 하기 때문이다. 그게 lazy Query 다.
react-router-dom 은 우리에게 3가지를 준다.
1. useHistory: 우리가 가고싶은곳을 가게해주고, change, replace, push 를 할 수 있게끔 만들어줘.
2. useLocation: 우리가 어디에 있는지 url 을 알게해주고.
3. useParams: 우리에게 파라미터를 준다.
Apollo cache 를 사용하는 방법
import { makeVar } from '@apollo/client';
Apollo Client에서 제공하는 makeVar를 통해 반응 변수라는 것을 만들 수 있는데,
이 반응 변수는 Apollo Client 캐시 외부에 로컬 상태를 저장하기 위해 사용된다.
export const currentPage = makeVar(1);
커스텀 페이지 네이션을 만들기 위해 위와 같은 코드를 사용하였는데.
export 해준다음 모든 컴포넌트에서 사용이 가능하다.
그러나, 남용하면 좋지 않기때문에 커스텀 하게 쓸수 있게 페이지네이션을 분리했다.
const page = useReactiveVar(currentPage);
useReactiveVar를 통해 직접 반응 변수에서 상태를 가져올 수 있다.
(이 경우에는 cache에 정의를 하지 않아도 되고 쿼리를 작성하지 않아도 된다).
useReactivevar() 에 currentPage 를 넣어준뒤, 커스텀 Component 에 프로퍼티로 내려주도록 해본다.
<PageNation data={data} pages={page} />
data 는 쿼리에서 받아온 객체, pages 에 위에서 정의한 page 를 내려준다.
커스텀 페이지네이션 도입부에 다음과 같이 프로퍼티를 정의해준다.
interface PageProps {
data: any;
pages: number;
}
export const PageNation = ({ data, pages }: PageProps) => {
data 는 안에 포함된 내부 객체가 달라질수 있기떄문에 타입을 정의하기 모호하다.
pages 같은경우는 오로지 number 로만 받아오기때문에 number 로 타이핑을 해준다.
const onPageClick = (val: number) => {
changePage(pages, val);
};
export const changePage = (page: number, value: number) => {
let result = page + value;
currentPage(result);
};
jest 초기사용시.
$npm test -- --coverage --watchAll=false
그러나 나는 특정파일만 test 하고 싶다.
예를들어 components , pages, routers 만 이라던가.
그럴경우 package.json 에 다음과 같이 작성.
...
"jest": {
"collectCoverageFrom": [
"./src/components/**/*.tsx",
"./src/pages/**/*.tsx",
"./src/routers/**/*.tsx"
]
}
components,pages,routers 를 collectCoverageFrom 안으로 넣어줬는데
collectCoverageFrom 는 커버리지 정보를 수집해야하는 파일 집합을 나타내는 glob 패턴 배열이다 .
파일이 지정된 glob 패턴과 일치하면이 파일에 대한 테스트가없고 테스트 스위트에서 필요하지 않은 경우에도
해당 파일에 대한 커버리지 정보가 수집된다.
front-jest
import { render } from '@testing-library/react';
render 에는 정말 많은 것을 사용하게 도와준다.
특히 debug 가 인상적이다.
describe('<App />', () => {
it("renders OK", () => {
const { debug } = render(<App />);
debug();
})
})
debug(); 호출시
앱의 구조가 훤히 보인다.
<body>
<div>
<span>
logged-out
</span>
</div>
</body>
container ?
컴포넌트를 debug() 로 찍어보면 알겠지만 child component 가 있을경우에 기대하는 값 까지도
test 의 일환이다. 사람은 알 수 있지만 컴퓨터는 모르기 때문 .
다음 예제를 보자 .
import { render } from '@testing-library/react';
import React from 'react';
import { Restaurant } from '../restaurant'
import { BrowserRouter as Router } from 'react-router-dom';
describe('<Restaurant>', () => {
it("renders OK with props", () => {
const restaurantProps = {
index: 1,
id: 1,
coverImg: "x",
name: "nameTest",
categoryName: "catTest"
}
const { getByText, container } = render(<Router>
{}
{ }
<Restaurant {...restaurantProps} />
</Router>)
getByText(restaurantProps.name)
getByText(restaurantProps.categoryName)
expect(container.firstChild).toHaveAttribute("href", `/restaurant/${restaurantProps.id}`)
})
})
apollo test ?
아폴로 테스트 ? 는 무었일까.
지금껏. gql 을 테스트해본 적은 없었다.
매번 내가 누구인지 인증하는 쿼리를 날릴빠엔, 커스텀 훅으로 만들어서
모든 컴포넌트에 심어주는게 좋다.
그런데 그 훅을 test 하는 과정이 문제였다.
hook 자체를 mock 하면 안되고 , hook 에 결과를 주는걸 mock 해야한다는 것이다.
hook 말고 서버에 보내는 `graphql` 의 request 를 mock 하면 된다.
따라서 apollo 측에서 제공 하는 두가지 방식을 소개하겠다.
1. 첫번째는 MockedProvider 로 풀어내는 방법
MockProvider 는 mocks 라는 prop 를 받는다.
mocks 는 query, mutation, result 를 mock 할 수 있게 해준다.
말 그대로 결과를 만들어서 테스트 할 수 있다는것이다.
예를 들어 , (주석에서 상세히 설명)
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
},
},
];
자 그럼 위에서 배운것처럼 실제 useMe 쿼리에 작성해보자.
완성하면 다음과 같은 코드가 MockProvider 에 mocks 프로퍼티에 심어지게 된다.
<MockedProvider mocks={[
{
request: {
query: ME_QUERY
},
result: {
data: {
me: {
id: 1,
email: "",
role: "",
verified: true,
}
}
}
}
]}>
그런다음 해야할게 남아있다.
원래 query 는 즉시 일어나는게 아니다.
query 가 끝나기 까지 시간이 좀 걸린다.
그래서 apollo 에서는 response 를 기다려서 해결하라고 알려준다.
await new Promise(resolve => setTimeout(resolve, 0));
it 실행 도입부에 async 를 붙여주고, 나서 저장을 하고, npm run test 를 해보면
`
console.error
Warning: An update to Header inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
`
위와 같은 에러가 나타나게 되는데 이 에러는 우리가 state 를 update 해서 나타나는것이다.
우리는 state update 도 기다려 줘야한다.
WaitFor 를 사용해 함수 내부에 기존 로직을 담으면 다음과 같다.
it("renders OK", async () => {
await waitFor(async () => {
const { debug } = render(
<MockedProvider mocks={[
{
request: {
query: ME_QUERY
},
result: {
data: {
me: {
id: 1,
email: "",
role: "",
verified: true,
}
}
}
}
]}>
<Router>
<Header />
</Router>
</MockedProvider>
);
await new Promise(resolve => setTimeout(resolve, 0));
})
이렇게 되면 우리가 website(react) 의 state 를 바꿔서 rerender 를 trigger 하니까,
rerender 를 기다려야하고 , Promise 도 기다려 줘야한다는 것이다.
})
이제 완성된 코드를 보자
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { Header } from '../header';
import { MockedProvider } from '@apollo/client/testing';
import { BrowserRouter as Router } from 'react-router-dom'
import { ME_QUERY } from '../../hooks/useMe'
describe('<Headr />', () => {
it("renders Verify Banner", async () => {
await waitFor(async () => {
const { getByText } = render(
<MockedProvider mocks={[
{
request: {
query: ME_QUERY
},
result: {
data: {
me: {
id: 1,
email: "",
role: "",
verified: false,
}
}
}
}
]}>
<Router>
<Header />
</Router>
</MockedProvider>
);
await new Promise(resolve => setTimeout(resolve, 0));
getByText("Please Verified your email.")
});
});
it("renders without Verify Banner", async () => {
await waitFor(async () => {
const { queryByText } = render(
<MockedProvider mocks={[
{
request: {
query: ME_QUERY
},
result: {
data: {
me: {
id: 1,
email: "",
role: "",
verified: true,
}
}
}
}
]}>
<Router>
<Header />
</Router>
</MockedProvider>
);
await new Promise(resolve => setTimeout(resolve, 0));
expect(queryByText("Please Verified your email.")).toBeNull();
});
});
})
ㅋㅋ 좀 길다.
getBy 는 element 를 찾지못하면 테스트에 실패한다.
qeuryBy 는 element 가 존재하지 않으면 테스트에 성공한다.
getBy 의 반대가 queryBy 다.
참고 Doc => https://www.apollographql.com/docs/react/development-testing/testing/
apollo testing 2
첫번째가 apollo graphql 이 제공하는 테스트방법으로 테스트했다면
두번째는 더좋은 테스팅 라이브러리를 도입하는것이다.
아폴로 graphql 이 제공하는 testing 방법으로는.
mutation 을 function 으로 테스트 할 수없다.
결과값만 테스트하는 과정에서
실수하고싶지 않아도 실수할 수 있다. + input variable 는 테스팅 할 수없다.
따라서 아래와 같은 라이브러리를 사용해보자.
$npm i mock-apollo-client
const mockedClient = createMockClient();
전에는 provider 전체를 mocking 했다면 지금은 client 만 mocking 해도 된다.
waitFor 는 state 가 바뀌는걸 await 하게 해준다.
waitFor() 에 user testing moudle 의 userEvent를 import 해서 사용 할 수 있다.
node
Callback Function 이 뭘까?
자바스크립트에서는, 함수(function)는 일급 객체입니다. 즉, 함수는 Object 타입이며 다른 일급객체와 똑같이 사용 될 수 있습니다. (String, Array, Number, 등등..) function 자체가 객체이므로 변수안에 담을 수 도 있고 인수로서 다른 함수에 전달 해 줄수도있고, 함수에서 만들어질수도있고 반환 될수도있습니다.
https://velopert.com/255
history 같은건 어떻게 jest 로 테스트 해야하나.
useHistory 같은 얘들은 엄~청 많은 함수들을 담고 있다,
그런데 어떻게 mocking 해야할까 ?
답은 간단하다.
해당 라이브러리를 mocking 한 후 스프레드 operator 를 사용하면 된다.
example )
const mockPushImplementation = jest.fn();
jest.mock('react-router-dom', () => {
const realModule = jest.requireActual('react-router-dom');
return {
...realModule,
useHistory: () => {
return {
push: mockPushImplementation,
}
}
}
})
마지막에
afterAll(() => {
jest.clearAllMocks()
})
해주는것 잊지말구.
다른데서 useHitory mocking 을 쓸 수도 있으니깐.
des......
what is Cypress ?
Cypress 는 windows , mac, linux 에서 멋진 end to end test 를
할 수 있게 도와주는 softWare 다.
step 1 )
npm install cypress
download done => npx cypress open
cypress 는 어떻게 움직일까 ?
npm run start 로 local server 를 띄워준뒤,
npx cypress open 을 해주면 (기본적으로 setting 을 안했을시)
example 들이 나온다.
root 디렉토링서 cypress 폴더를 찾아준뒤 , integration 폴더를 열어서
모두 지워준다. ( 괜찮다. cypress 홈페이지에 예제 차고 넘친다.)
e2e 테스트 대상 ( login 부터 ) 페이지 테스트 파일을 만들어주는데 ,
.spec 이 굳이 안붙어줘도 될뿐더러 , cypress 소프트 웨어가 관장하는 웹페이지 가 아주 경이롭기 짝이없다.
step 2 )
test 하는 방법은 jest 와 비슷하다.
just describe , it 의 향연.
describe('Log in', () => {
it('should see Login Page', () => {
cy.visit('/').title().should('eq', 'Login | Nuber Eats');
});
다른점이 있다면 .
cy.visit 을 써줘야 한다는점 . 그 뒤론 비슷비슷하고 ,
form 요소 의 class name 을 가져오는 과정에서 dom 을 testing 하기 쉽지 않기 때문에 .
다음 과 같은 lib 를 다운받아준다.
npx install @testing-library/cypress --save -dev
그러면 jest 의 getByText 와 같은 친구를 데려올 수 있다.
class name 을 가져오는게 아니라 , text 로 테스트를 할 수 있다.
그런다음 cypress > tsconfig 에
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "../node_mudules",
"types": [
"cypress",
"@testing-library/cypress" // 이부분을 추가 해 주자.
],
"outDir": "#"
},
"include": ["./**/*.*"]
}