JS/TS GraphQL環境でFactoryBot Fakerのような環境を作った

テスト書くとき、mock定義面倒ですよね!!!

---

storybookでも同じコード書いて...
mockだけで100行超えてしまうと本来書くコードが何か分からなくなったりしますよね😇😇😇😇😇

---

RailsのFactoryBotのような環境を作りましたので紹介します

---

## 紹介するもの完成形(使用方法)

---

```index.stories.tsx
import { createList } from '../factory_helper'
import { newUser, newUserEmail } from '../factories'

Default.args = {
  fragment: {
    users: createList(newUser, 3), // [{__typename: 'User', id: 'u:1', nickname: 'Jerrell49', phone: '(574) 708-9283', userEmails: Array(1)}, ...]
    email: newUserEmail() // {__typename: 'UserEmail', id: 'ue:1', email: 'Lucile.Hagenes@gmail.com'}
  },
}

```

---

カスタマイズしたい場合、`newUser({ nickname: '山田' })`のように書きます

```
> newUser({ nickname: '山田' })
{__typename: 'User', id: 'u:1', nickname: '山田', phone: '(574) 708-9283', userEmails: Array(1)}
```

---

## 今回使用例のschema

---

```schema.graphql
type User {
  id: ID!
  userEmails: [UserEmail]!
  nickname: String!
  phone: String!
}

type UserEmail {
  id: ID!
  email: String
}
```
---

## `@faker-js/faker` をいれる
呼び出されるたびに結果が変わる

```bash
$ yarn add --dev @faker-js/faker

node
> faker.internet.userName()
'Abdiel_Volkman53'
> faker.internet.userName()
'Jerrell49'
```
---

## `/factories`にfactoryを定義する

---

```/factories/UserEmail.ts
import { faker } from '@faker-js/faker'
import { UserEmail } from '../graphql/generated'
import { nextFactoryId } from '../factory_helper'

export interface UserEmailOptions {
  __typename?: 'UserEmail'
  id?: UserEmail['id']
  email?: UserEmail['email']
}

export const newUserEmail = (options: UserEmailOptions = {}): UserEmail => {
  const o = options as UserEmail
  o.__typename = 'UserEmail'
  o.id = options.id ?? nextFactoryId('UserEmail')
  o.email = options.email ?? faker.internet.email()

  return o
}

```

---

```/factories/User.ts
import { faker } from '@faker-js/faker'
import { User, UserEmail } from '../graphql/generated'
import { newUserEmail } from '.'
import { nextFactoryId } from '../factory_helper'

export interface UserOptions {
  __typename?: 'User'
  id?: User['id']
  nickname?: User['nickname']
  phone?: User['phone']
  userEmails?: Array<UserEmail>
}

export const newUser = (options: UserOptions = {}): User => {
  const o = options as User
  o.__typename = 'User'
  o.id = options.id ?? nextFactoryId('User')
  o.nickname = options.nickname ?? faker.internet.userName()
  o.phone = faker.phone.number()
  o.userEmails = [newUserEmail({ user: o })]

  return o
}

```
---

こんなように`index.ts`にとめると便利ですね

```/factories/index.ts
export { newUser, type UserOptions } from './User'
export { newUserEmail, type UserEmailOptions } from './UserEmail'
```

---

## FactoryBotによくあるhelperを作る

---

createListを作りました (npm packageにしたいね)

```/factory_helper.ts
import { newUser, UserOptions, newUserEmail, UserEmailOptions } from './factories'

export type FactoryFunction = typeof newUser | typeof newUserEmail

export type NodeOptions =
  | UserOptions
  | UserEmailOptions

export const createList = (fn: FactoryFunction, i: number, options: NodeOptions = {}) => [...Array(i)].map(() => fn(options as any))

let nextFactoryIds: Record<string, number> = {};
const taggedIds: Record<string, string> = {};

export const nextFactoryId = (objectName: string): string => {
  const nextId = nextFactoryIds[objectName] || 1;
  nextFactoryIds[objectName] = nextId + 1;
  const tag = taggedIds[objectName] ?? objectName.replace(/[a-z]/g, "").toLowerCase();
  return tag + ":" + nextId;
}

```

---

# 参考
https://github.com/homebound-team/graphql-typescript-factories
scalarが読み込めなかったり、optionsをコミットしたかったので使用せず、ファイルを作成する形にした。 
associationにて親子ともにを定義した場合重くなるので、方法模索中...(メソッド叩くまでクエリ呼ばれないとか、letとかrailsすごかった)
案としてはfragmentからfacotryを生成できるやつをどうにか頑張る(resoverに適当させる)

# 余談(Rails環境でのFactoryの便利さ)
とあるページがみたいが、userデータが必要なとき
```ruby
User.create!(email: 'test@example.com', address: '東京都' ...大量のvalidates項目を入力)
# => nicknameが入力されていません。😡
FactoryBot.create(:user)
# => 簡単！！
```
herokuなどのpreview app環境でもfactoryからユーザーを大量に作ったりしています
User.create!()のようにheroku preview環境のテストコードのデータを書いた場合、validatesの変更で動かなくなり、メンテナンスされてないことを防げます。

Rails.envでproductionを使いながら、gemを追加する方法はこちら→[productionで動かしながら、サーバーによって専用Gemを追加
](https://qiita.com/kyohah/items/6ab9a433eaaf84392041)