テスト書くとき、mock定義面倒ですよね!!!
storybookでも同じコード書いて...
mockだけで100行超えてしまうと本来書くコードが何か分からなくなったりしますよね😇😇😇😇😇
RailsのFactoryBotのような環境を作りましたので紹介します
紹介するもの完成形(使用方法)
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
type User {
id: ID!
userEmails: [UserEmail]!
nickname: String!
phone: String!
}
type UserEmail {
id: ID!
email: String
}
@faker-js/faker
をいれる
呼び出されるたびに結果が変わる
$ yarn add --dev @faker-js/faker
node
> faker.internet.userName()
'Abdiel_Volkman53'
> faker.internet.userName()
'Jerrell49'
/factories
にfactoryを定義する
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
}
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
にとめると便利ですね
export { newUser, type UserOptions } from './User'
export { newUserEmail, type UserEmailOptions } from './UserEmail'
FactoryBotによくあるhelperを作る
createListを作りました (npm packageにしたいね)
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データが必要なとき
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を追加