GraphQL Data Mocking

이번 포스팅에서는 GraphQL을 사용할때 Data를 Mocking하는 방법에 대해서 소개해보려고 한다.

Mocking

Mocking은 주로 테스팅에서 많이 사용되는 용어이다. Unit Test를 작성할때 테스트하기 어려운 상황에서 행위검증을 위해 주로 사용된다. 자세하게 표현하면 stub, spy와 같은 용어들이 있지만 여기서는 가짜 데이터를 만들어내는 행위를 Data에 대한 Mocking이라고 표현하겠다.

Data Mocking?

웹개발은 Frontend, Backend 로 파트가 나뉘어져 파트별 개발자들의 협력으로 개발하는 경우가 많다.
이런식으로 나뉘어져 개발할 경우 Frontend 개발자의 작업은 Backend Data에 의존적이다. API 호출을 통해서 받아온 데이터를 통해 UI가 그려지는 경우가 대부분이기 때문이다.
이렇게 파트별 Task간의 의존성의 있는경우 병렬적으로 개발하지 못해서 생산성이 저하되는 단점이 있다. 이러한 이유로 API가 개발 완료되지 않은 상황이라면 Frontend 개발시 자체적으로 API 호출하는 부분을 분리하고 Mocking해서 사용하거나 Fake Data를 import 해서 진행하기도 한다.

간단한 예시를 보자. 아래 코드는 TodoList를 만든다고 가정하고 작성하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react'
// fake data import
import todos from './fixture/todos'
// or API call
// import getTodos from './api/todo'

const TodoList = ({ todos }) => (
<ul>
{
todos.map({ id, title, completed } => ({
<li key={id}>{title}</li>
}))
}
</ul>
)

const App = () => (
<div>
<TodoList todos={todos} />
</div>
)

위 코드를 보면 fixture 디렉토리에 Fake Data를 미리 정의해놓고 UI를 확인하거나 API call하는 부분을 분리해서 test시에는 Fake Data를 반환하는 promise로 작성해놓고 사용할 수 있다. 이렇게 Frontend에서 Data를 mocking해서 개발할 경우 실제 API가 개발되면 대부분 코드가 변경되고 다시 테스트해야되는 상황이 생길 수 있다.

GraphQL Data Mocking

Frontend와 Backend가 병렬적으로 작업할때 좀 더 매끄럽게 진행하는 방법은 없을까?
GraphQL 을 사용하면 Type System을 활용해서 손 쉽게 Data Mocking하는 기능을 사용할 수 있다.
(GraphQL 구현체마다 지원할수도 안할수도 있다.)

간단한 예제를 만들면서 GraphQL Mocking이 어떤식으로 작동하는지 살펴보도록 하자.

TodoList SDL

1
2
3
4
5
6
7
8
9
10
11
type Todo {
id: Int!
title: String!
completed: Boolean!
createdAt: String!
updatedAt: String!
}

type Query {
todos: [Todo]!
}

위와 같이 GraphQL 은 SDL상에 Type이 명시되어 있다.
Mock Data를 만들때 이 Type을 이용해서 쉽게 DataSet을 구성할 수 있다.
GraphQL Data Mocking이 편리한 이유이기도 하다.

Type에 대한 Mock Resolver를 만드는 예시이다.

1
2
3
4
5
{
Int :() => faker.random.number(100),
String :() => faker.random.word(),
Boolean :() => faker.random.boolean(),
}

이렇게 Query나 Field에 대한 Resolver가 아닌 Type에 Resolver를 붙여서 사용이 가능하다.
Int, String, Boolean Type으로 명시되어 있는 field는 모두 해당 resolver에 의해서 data가 만들어지게된다.
ex) id - 1 ~ 100 난수
title - random word

이건 GraphQL에 있는 기본 Scalar에 대해서만 작성했는데 Custom Type에 대해서도 설정이 가능하고 자유도 높게 사용이 가능하다.

1
2
3
4
Todo: () => ({
createdAt:() => new Date().toDateString(),
updatedAt:() => new Date().toDateString()
}),

위와 같이 특정 Type, Field에 대해서만 다른 resolver를 지정할 수도 있다.
(Custom Date Scalar를 만들고 Date에 Resolver사용도 가능하다. 이 예제에서는 편의상 간단하게 작성했다.)

List Type의 경우에는 MockList 를 이용할 수 있다.
아래 코드는 todos Query가 몇개의 데이터를 리턴할지 결정하는 부분이다.

1
2
3
4
Query: () => ({
todos: new MockList(20)
// or range new MockList([1, 20])
})

아래 코드는 동작하는 예제의 전체 코드이다.
FULL CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const { makeExecutableSchema, addMockFunctionsToSchema, MockList } = require('graphql-tools')
const faker = require('faker')
const { ApolloServer } = require('apollo-server')

const typeDefs = `
type Todo {
id: Int!
title: String!
completed: Boolean!
createdAt: String!
updatedAt: String!
}

type Query {
todos: [Todo]!
}
`

const mocks = {
Int :() => faker.random.number(100),
String :() => faker.random.word(),
Boolean :() => faker.random.boolean(),
Todo: () => ({
createdAt:() => new Date().toDateString(),
updatedAt:() => new Date().toDateString()
}),
Query: () => ({
todos: () => new MockList(100)
})
}

const schema = makeExecutableSchema({
typeDefs
})

addMockFunctionsToSchema({ schema, mocks, preserveResolvers: true })

const server = new ApolloServer({ schema, mocks })

server.listen(3000)

Result Dataset 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"data": {
"todos": [
{
"id": 18,
"title": "Bedfordshire",
"completed": true,
"createdAt": "Sun Nov 17 2019"
},
{
"id": 92,
"title": "payment",
"completed": false,
"createdAt": "Sun Nov 17 2019"
},
{
"id": 5,
"title": "National",
"completed": true,
"createdAt": "Sun Nov 17 2019"
},
{
"id": 5,
"title": "Plastic",
"completed": false,
"createdAt": "Sun Nov 17 2019"
},
{
"id": 6,
"title": "Devolved",
"completed": true,
"createdAt": "Sun Nov 17 2019"
},
...
]
}
}

기존에 Apollo Server가 구성되어 있다면 schema는 이미 있는 상태이고 여기에 mocksaddMockFunctionsToSchema 만 작성해주면 된다.

GraphQL은 BaseType과 이를 조합한 Type들로 구성되어 있다.
즉 몇가지 타입들에 대한 Mock Resolver를 구현해놓으면 더 이상 Dataset을 만드는데 시간을 낭비할 필요가 없어진다.

Mock Server를 구성하는 방법도 다양하게 선택이 가능해보인다.

  • 기존 Apollo Server에 구현되지 않은 Resolver에 대해서만 Mock을 사용
  • 다른 Remote GraphQL Server의 schema를 받아와서 별도의 Mock Server를 구현
  • Server 없이 Apollo Client에서 Mock Schema Link 이용

더 자세한 사항은 공식문서를 참고하도록 하자.

마치며

위와 같이 Mock Server를 구성한다면 API 때문에 Frontend 코드가 변경되는 것을 최소화 할 수 있다.
server에서도 얼마 안되는 코드로 mock data를 생성하고 관리하기 때문에 부하가 큰 작업은 아니다.
client에 fake data를 구성하는 방법으로도 충분할 수 있지만 GraphQL을 사용한다면 시도해볼만한 것 같다.

REST API를 사용중이라면 json-server와 같은 도구가 비슷한 역할을 할 수 있다.

Ref