์ค์ ํ๋ก๋์
๋ฐ์ดํฐ๋ฅผ Fixture DB๋ก ๊ฐ์ ธ์์ ํ
์คํธ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ด
๋๋ค.
Fixture ๋ก๋ฉ ๊ฐ์
์ค์ ๋ฐ์ดํฐ ํ๋ก๋์
๋ฐ์ดํฐ ํ์ค์ ์ธ ํ
์คํธ
๊ด๊ณ ์๋ ํด๊ฒฐ BelongsTo ์๋ ์ถ์ ์ฐ๊ฒฐ๋ ๋ฐ์ดํฐ ํจ๊ป
์ค๋ณต ๋ฐฉ์ง ID ๊ธฐ๋ฐ ๊ด๋ฆฌ ์์ ํ ๊ฐ์ ธ์ค๊ธฐ
๊ฐํธํ ๋ช
๋ น์ด ํ ์ค๋ก ์คํ ์๋ ๋๊ธฐํ
pnpm sonamu fixture import
ํ๋ก๋์
DB์ ํน์ ๋ ์ฝ๋๋ฅผ Fixture DB๋ก ๊ฐ์ ธ์ต๋๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
pnpm sonamu fixture import [entityId] [recordIds]
ํ๋ผ๋ฏธํฐ :
entityId: Entity ์ด๋ฆ (์: โUserโ, โPostโ)
recordIds: ๊ฐ์ ธ์ฌ ๋ ์ฝ๋ ID ๋ฐฐ์ด (์ผํ๋ก ๊ตฌ๋ถ)
# User ID 1, 2, 3์ Fixture DB๋ก ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import User 1,2,3
# Post ID 42๋ฅผ Fixture DB๋ก ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import Post 42
์๋ ์๋ฆฌ
1. ๋ ์ฝ๋ ์ถ์ถ
// sonamu/src/testing/fixture-manager.ts
async function importFixture ( entityId : string , ids : number []) {
const queries = await Promise . all (
ids . map ( async ( id ) => {
// ๊ฐ ID์ ๋ํ import ์ฟผ๋ฆฌ ์์ฑ
return await this . getImportQueries ( entityId , "id" , id );
}),
);
// ์ฟผ๋ฆฌ ์คํ
const wdb = BaseModel . getDB ( "w" );
for ( const query of queries ) {
await wdb . raw ( query );
}
}
2. ๊ด๊ณ ์๋ ์ถ์
BelongsTo ๊ด๊ณ๋ฅผ ์๋์ผ๋ก ์ถ์ ํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์ต๋๋ค.
# User #1์ ๊ฐ์ ธ์ค๋ฉด...
pnpm sonamu fixture import User 1
# ์๋์ผ๋ก ๊ฐ์ ธ์ด:
# - User #1
# - User #1์ Profile (OneToOne)
# - Profile์ ๊ด๋ จ ๋ฐ์ดํฐ
๋ด๋ถ ๋์ :
async function getImportQueries ( entityId : string , field : string , id : number ) : Promise < string []> {
// ์ํ ์ฐธ์กฐ ๋ฐฉ์ง
const recordKey = ` ${ entityId } # ${ field } # ${ id } ` ;
if ( this . visitedRecords . has ( recordKey )) {
return [];
}
this . visitedRecords . add ( recordKey );
// ๋ ์ฝ๋ ๊ฐ์ ธ์ค๊ธฐ
const entity = EntityManager . get ( entityId );
const [ row ] = await wdb ( entity . table ). where ( field , id ). limit ( 1 );
if ( ! row ) {
throw new Error ( ` ${ entityId } # ${ id } row๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.` );
}
// BelongsTo ๊ด๊ณ ์ฒ๋ฆฌ
const relatedQueries = [];
for ( const [, relation ] of Object . entries ( entity . relations )) {
if ( isBelongsToOneRelationProp ( relation )) {
const relatedId = row [ ` ${ relation . name } _id` ];
if ( relatedId ) {
// ์ฌ๊ท์ ์ผ๋ก ๊ด๋ จ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
relatedQueries . push ( ... ( await this . getImportQueries ( relation . with , "id" , relatedId )));
}
}
}
// INSERT ์ฟผ๋ฆฌ ์์ฑ
const selfQuery = `INSERT IGNORE INTO fixture. ${ entity . table }
(SELECT * FROM production. ${ entity . table } WHERE id = ${ id } )` ;
return [ ... relatedQueries , selfQuery ];
}
3. ์๋ ๋๊ธฐํ
import ํ ์๋์ผ๋ก Fixture DB โ Test DB ๋๊ธฐํ๊ฐ ์คํ๋ฉ๋๋ค.
async function fixture_import ( entityId : string , recordIds : number []) {
await setupFixtureManager ();
// 1. ํ๋ก๋์
โ Fixture DB
await FixtureManager . importFixture ( entityId , recordIds );
// 2. Fixture DB โ Test DB
await FixtureManager . sync ();
}
์คํ ์์
์ฌ์ฉ์ ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import User 1,2,3
{ entityId: 'User', field: 'id', id: 1 }
{ entityId: 'Profile', field: 'id', id: 1 }
INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...
{ entityId: 'User', field: 'id', id: 2 }
{ entityId: 'Profile', field: 'id', id: 2 }
INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...
โ
Import complete! Syncing to test DB...
๊ฒ์๊ธ ๊ฐ์ ธ์ค๊ธฐ
$ pnpm sonamu fixture import Post 42
{ entityId: 'Post', field: 'id', id: 42 }
{ entityId: 'User', field: 'id', id: 5 }
{ entityId: 'Profile', field: 'id', id: 5 }
{ entityId: 'Category', field: 'id', id: 10 }
INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...
INSERT IGNORE INTO fixture.categories ...
INSERT IGNORE INTO fixture.posts ...
โ
Import complete!
๋ณต์กํ ๊ด๊ณ ์ฒ๋ฆฌ
HasMany ๊ด๊ณ
# User #1๊ณผ ๊ทธ์ ๋ชจ๋ Posts๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด
# 1. User ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import User 1
# 2. User์ Posts๋ฅผ ๋ณ๋๋ก ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import Post 10,11,12
# HasMany๋ ์๋์ผ๋ก ์ถ์ ๋์ง ์์
# (๋๋ฌด ๋ง์ ๋ฐ์ดํฐ๊ฐ ๊ฐ์ ธ์์ง ์ ์์)
ManyToMany ๊ด๊ณ
# Post #1๊ณผ ๊ด๋ จ Tags๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด
# 1. Post ๊ฐ์ ธ์ค๊ธฐ
pnpm sonamu fixture import Post 1
# 2. ์ค๊ฐ ํ
์ด๋ธ ๋ฐ์ดํฐ๋ ์๋์ผ๋ก ์ฒ๋ฆฌ
# (๋๋ fixture.ts์์ ์ง์ ์์ฑ)
์ค๋ณต ์ฒ๋ฆฌ
INSERT IGNORE๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ณต์ ๋ฐฉ์งํฉ๋๋ค.
# User #1์ ๋ ๋ฒ ๊ฐ์ ธ์๋ ์์
pnpm sonamu fixture import User 1
pnpm sonamu fixture import User 1
# ๊ฒฐ๊ณผ: User #1์ ํ ๋ฒ๋ง ์ ์ฅ๋จ
์ํ ์ฐธ์กฐ ๋ฐฉ์ง
๋ฐฉ๋ฌธํ ๋ ์ฝ๋๋ฅผ ์ถ์ ํ์ฌ ์ํ ์ฐธ์กฐ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
// ์ํ ์ฐธ์กฐ ์์:
// User #1 โ Profile #1
// Profile #1 โ User #1 (์ญ์ฐธ์กฐ)
// ๋ฐฉ๋ฌธ ๊ธฐ๋ก์ผ๋ก ๋ฐฉ์ง
private visitedRecords = new Set < string >();
const recordKey = ` ${ entityId } # ${ field } # ${ id } ` ;
if ( this . visitedRecords . has ( recordKey )) {
return []; // ์ด๋ฏธ ๋ฐฉ๋ฌธํจ - ์คํต
}
this . visitedRecords . add ( recordKey );
๋ฒ ์คํธ ํ๋ํฐ์ค
1. ์ต์ ๋ฐ์ดํฐ
# โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ํ์ํ ๊ฒ๋ง
pnpm sonamu fixture import User 1,2
# โ ์๋ชป๋ ๋ฐฉ๋ฒ: ๋๋ฌด ๋ง์ ๋ฐ์ดํฐ
pnpm sonamu fixture import User 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
2. ๋ํ ์ผ์ด์ค
# ๋ค์ํ ์ผ์ด์ค๋ฅผ ํฌํจํ๋ ๋ฐ์ดํฐ ์ ํ
pnpm sonamu fixture import User 1 # ์ผ๋ฐ ์ฌ์ฉ์
pnpm sonamu fixture import User 100 # ๊ด๋ฆฌ์
pnpm sonamu fixture import User 500 # ํ๋ฆฌ๋ฏธ์ ์ฌ์ฉ์
3. fixture.ts์ ํจ๊ป ์ฌ์ฉ
// api/src/testing/fixture.ts
import { createFixtureLoader } from "sonamu/test" ;
export const loadFixtures = createFixtureLoader ({
// ํ๋ก๋์
์์ ๊ฐ์ ธ์จ ์ค์ ๋ฐ์ดํฐ ์ฌ์ฉ
realUser01 : async () => {
const userModel = new UserModel ();
// ID 1์ ์ด๋ฏธ fixture DB์ ์๋ค๊ณ ๊ฐ์
const { user } = await userModel . getUser ( "C" , 1 );
return user ;
},
// ํ
์คํธ์ฉ ์ ๋ฐ์ดํฐ ์์ฑ
testUser : async () => {
const userModel = new UserModel ();
const { user } = await userModel . create ({
username: "test" ,
email: "test@example.com" ,
password: "password" ,
});
return user ;
},
});
๋ฌธ์ ํด๊ฒฐ
๋ ์ฝ๋๋ฅผ ์ฐพ์ ์ ์์
Error: User#999 row๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
ํด๊ฒฐ :
ํ๋ก๋์
DB์ ํด๋น ID๊ฐ ์กด์ฌํ๋์ง ํ์ธ
์ฌ๋ฐ๋ฅธ Entity ์ด๋ฆ ์ฌ์ฉ
๊ถํ ์ค๋ฅ
Error: Access denied for user 'readonly'
ํด๊ฒฐ :
-- Fixture DB ์ฌ์ฉ์์๊ฒ ์ฐ๊ธฐ ๊ถํ ๋ถ์ฌ
GRANT ALL PRIVILEGES ON fixture. * TO 'user' @ '%' ;
๋๋ฌด ๋ง์ ๋ฐ์ดํฐ
# ํ ๋ฒ์ ๋๋ฌด ๋ง์ ID๋ฅผ ๊ฐ์ ธ์ค๋ฉด ๋๋ฆผ
pnpm sonamu fixture import User 1,2,3,...,100
# ํด๊ฒฐ: ์ฌ๋ฌ ๋ฒ ๋๋ ์ ์คํ
pnpm sonamu fixture import User 1,2,3,4,5
pnpm sonamu fixture import User 6,7,8,9,10
์ฃผ์์ฌํญ
Fixture ๋ก๋ฉ ์ ์ฃผ์์ฌํญ : 1. ์ต์ ๋ฐ์ดํฐ : ํ์ํ ์ต์ํ์ ๋ ์ฝ๋๋ง ๊ฐ์ ธ์ค๊ธฐ 2. ๊ด๊ณ
ํ์ธ : BelongsTo๋ ์๋, HasMany๋ ์๋ 3. ์ค๋ณต ์์ : INSERT IGNORE๋ก ์ค๋ณต ๋ฐฉ์ง 4. ์ํ
์ฐธ์กฐ : ์๋์ผ๋ก ์ฒ๋ฆฌ๋จ 5. ๊ถํ ํ์ธ : Fixture DB์ ์ฐ๊ธฐ ๊ถํ ํ์
๋ค์ ๋จ๊ณ
ํ
์คํธ DB ํ
์คํธ DB ์ค์
Fixture ์์ฑ fixture.ts ์์ฑํ๊ธฐ
Fixture ๋๊ธฐํ Fixture โ Test DB
ํ
์คํธ ์์ฑ ํ
์คํธ ๊ตฌ์กฐ ์ดํดํ๊ธฐ