๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

[ํŠน๊ฐ• ๋‚ด์šฉ ์ •๋ฆฌ]ํ…Œ์ŠคํŠธ์ฝ”๋“œ - ์‹ค์Šต

by _silver 2024. 12. 16.

๐Ÿ“š Mocking์ด๋ž€?

- Mocking์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, ์‹ค์ œ ๊ฐ์ฒด๋‚˜ ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ํ‰๋‚ด ๋‚ด๋Š”(Mock) ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ

- ์‰ฝ๊ฒŒ ๋งํ•ด, ์‹ค์ œ ์„œ๋น„์Šค๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์งœ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ๊ฑธ ์ด์šฉํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.


โ“์™œ Mocking์ด ํ•„์š”ํ•œ๊ฐ€?

1. ์™ธ๋ถ€ ์ž์›์— ์˜์กด์ ์ธ ๋กœ์ง ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด

Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„œ๋น„์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ณดํ†ต DB๋‚˜ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

- ๋งŒ์•ฝ ์‹ค์ œ DB๋‚˜ ์™ธ๋ถ€ API๋ฅผ ๋งค๋ฒˆ ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ธํŒ…์ด ๋ฒˆ๊ฑฐ๋กญ๊ณ , ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋‚˜ DB ์…‹์—… ๋ฌธ์ œ ๋“ค์œผ๋กœ ์ธํ•ด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๊ฑฐ๋‚˜, ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค.

- ์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ ๊ฐ€์งœ๋กœ DB๋ฅผ ํ‰๋‚ด ๋‚ด๋Š” Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, DB ์—ฐ๊ฒฐ ์—†์ด๋„ 'DB๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •'ํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ•ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

2. ๋…๋ฆฝ์ ์ด๊ณ  ๋น ๋ฅธ ํ…Œ์ŠคํŠธ

ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” ์ž‘์€ ๋‹จ์œ„(๋‹จ์œ„ํ…Œ์ŠคํŠธ, ์œ ๋‹›ํ…Œ์ŠคํŠธ)๋กœ ๋น ๋ฅด๊ฒŒ ๋Œ์•„์•ผ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค.

- ํ•˜์ง€๋งŒ, ๋ชจ๋“  ์˜์กด ๊ฐ์ฒด๋ฅผ ์‹ค์ œ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด ์†๋„๊ฐ€ ๋А๋ ค์ง€๊ณ  ๋‹ค๋ฅธ ์š”์†Œ๋“ค(๋„คํŠธ์›Œํฌ๋‚˜ DB ์ƒํƒœ)์— ์˜์กด์ ์ด ๋˜์–ด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ์ผ๊ด€์ ์ด์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

- Mocking์„ ํ†ตํ•ด ์„œ๋น„์Šค์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ณ , ํ…Œ์ŠคํŠธ ์†๋„๋„ ๋งค์šฐ ๋นจ๋ผ์ ธ ์‹ ๋ขฐ๋„๊ฐ€ ๋†’์•„์ง„๋‹ค.

 

3. ๋ณต์žกํ•œ ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ ์ผ์ฐ ๋ฐœ๊ฒฌ ๊ฐ€๋Šฅ

- ํ…Œ์ŠคํŠธํ•  ๋•Œ ์™ธ๋ถ€ ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ•จ์œผ๋กœ์จ, ๋‚ด ์ฝ”๋“œ๊ฐ€ ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

- ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฌธ์ œ๋ฅผ ๋‹ค๋ฅธ ๊ณณ(DB ์„ค์ • ์˜ค๋ฅ˜ ๋“ฑ)์—์„œ ์ฐพ๊ธฐ ๋ณด๋‹ค๋Š” ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ๋กœ์ง์—์„œ ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.


โญ๏ธ Test์—์„œ Mocking์ด ์ค‘์š”ํ•œ ์ด์œ ๋Š”?

1. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(์œ ๋‹› ํ…Œ์ŠคํŠธ)์˜ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ

- ์‹ค์ œ DB, ๋„คํŠธ์›Œํฌ, ํŒŒ์ผ ์‹œ์Šคํ…œ ๋“ฑ ์™ธ๋ถ€ ์˜์กด์„ฑ๊ณผ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์†๋„๊ฐ€ ๋น ๋ฅด๊ณ , ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

→ ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋‚˜ DB ์—ฐ๊ฒฐ์— ์˜์กดํ•˜์ง€ ์•Š๊ณ ๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•ญ์ƒ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋„๋ก ํ•œ๋‹ค.

 

2. ํ…Œ์ŠคํŠธ ๋ฒ”์œ„์™€ ์ •ํ™•์„ฑ ํ–ฅ์ƒ

- ์›ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค(์„ฑ๊ณต์ผ€์ด์Šค, ์˜ˆ์™ธ์ผ€์ด์Šค)๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค์ •ํ•˜๊ณ , ํ•ด๋‹น ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•œ ์‘๋‹ต์„ Mock ๊ฐ์ฒด์—์„œ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด ๋‹ค์–‘ํ•œ ์ผ€์ด์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์šฉ์ดํ•˜๋‹ค.

→ ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹œ DB๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™ฉ ์ด๋ผ๋Š” ์ผ€์ด์Šค๋ฅผ ์‹ค์ œ DB ์„ธํŒ… ์—†์ด๋„ Mock์œผ๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

3. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด

- Mock์„ ์‚ฌ์šฉํ•ด ๋ณต์žกํ•œ ์˜์กด์„ฑ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋Œ€์ฒดํ•˜๋ฉด ํ…Œ์ŠคํŠธ์ฝ”๋“œ๊ฐ€ ๊ฐ€๋ฒผ์›Œ์ง€๊ณ  ์ง๊ด€์ ์œผ๋กœ ๋œ๋‹ค.

- ์„œ๋น„์Šค ๋กœ์ง ๋ณ€๊ฒฝ ์‹œ์—๋„, ์‹ค์ œ ์ธํ”„๋ผ ์„ค์ •์„ ๋ณ€๊ฒฝํ•  ํ•„์š” ์—†์ด Mock ์‘๋‹ต๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๋ฏ€๋กœ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฝ๋‹ค.

 

4. ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„

- TDD๋‚˜ BDD ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ Mock์„ ์‚ฌ์šฉํ•˜๋ฉด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๊ตฌ๋™ํ•˜์ง€ ์•Š๊ณ ๋„ ํŠน์ • ๊ณ„์ธต(Controller, Service)๋งŒ ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

- ๋นŒ๋“œ ํŒŒ์ดํ”„๋ผ์ธ์—์„œ๋„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ฐ€์žฅ ๋จผ์ € ์ˆ˜ํ–‰๋˜๋ฉฐ, ๋น ๋ฅด๊ฒŒ ์‹คํŒจ ๋˜๋Š” ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ค€๋‹ค.

 

5. ๊ฒฐํ•ฉ๋„(Coupling) ๋‚ฎ์ถ”๊ธฐ

- Mocking์€ ๊ฐ์ฒด ๊ฐ„ ๊ฒฐํ•ฉ์„ ๋‚ฎ์ถ”๊ณ , ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜ ์„ค๊ณ„๋ฅผ ๋…๋ คํ•œ๋‹ค.

- SOLID ์›์น™ ์ค‘ DIP(Dependency Inversion Principle)๋ฅผ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค.

 

6. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฒ€์ฆ์— ์ง‘์ค‘

- ์™ธ๋ถ€ ์‹œ์Šคํ…œ/๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋™์ž‘๊นŒ์ง€ ๊ณ ๋ คํ•˜์ง€ ์•Š๊ณ , ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ์˜ ๋กœ์ง๋งŒ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ "๋ฌด์—‡์ด" ๋ฌธ์ œ์ธ์ง€ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.

→ EmailService๊ฐ€ ๊ณผ๊ธˆ ์‹œ์Šคํ…œ๊ณผ ์–ด๋–ป๊ฒŒ ์—ฐ๋™๋˜๋Š”์ง€๋ฅผ ์ „๋ถ€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธํ•˜์ง€ ์ „์—, EmailService์˜ ํ•ต์‹ฌ ๋กœ์ง๋งŒ Mock SMTP ์„œ๋ฒ„๋กœ ํ…Œ์ŠคํŠธ ํ•ด๋ณธ๋‹ค.


๐Ÿšจ Mocking ์‚ฌ์šฉ์‹œ ์ฃผ์˜ํ•  ์ 

1. ๊ณผ๋„ํ•œ Mock ๋‚จ๋ฐœ

- ๋ชจ๋“  ์˜์กด์„ฑ์„ Mock์œผ๋กœ ๋Œ€์ฒดํ•˜๋ฉด, ์‹ค์ œ ์ฝ”๋“œ์™€์˜ ๋™์ž‘ ์ฐจ์ด๋ฅผ ๋†“์น  ์ˆ˜ ์žˆ๋‹ค.

- ๋ถ„๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ๋งŒ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฆฌ์Šคํฌ๊ฐ€ ์žˆ๋‹ค.

 

2. Mock ๊ฐ์ฒด์˜ ์ž˜๋ชป๋œ ์„ค์ •(Over-stubbing)

- ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์€ when() ์กฐ๊ฑด์„ ์„ค์ •ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์ง€๋‚˜์น˜๊ฒŒ ์„ธ๋ถ€ ๊ตฌํ˜„์— ์˜์กด์ ์ด๊ฒŒ ๋œ๋‹ค.

- ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง ์‹œ Mock ์„ค์ • ๋ถ€๋ถ„์ด ๊นจ์งˆ ํ™•๋ฅ ์ด ๋†’์•„์ง„๋‹ค.

 

3. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜

- Mock์œผ๋กœ ์ธํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด, ์‹ค์ œ ์‹œ์Šคํ…œ์ด ์•„๋‹Œ "Mock ์‹œ๋ฎฌ๋ ˆ์ด์…˜"๋งŒ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋Œ๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

- ํ•ญ์ƒ "์‹ค์ œ ๋กœ์ง" ๊ณผ "Mock ์„ค์ •" ๊ฐ„์— ๊ฐญ์ด ์—†๋Š”์ง€ ์ ๊ฒ€์ด ํ•„์š”ํ•˜๋‹ค.

 

4. ์ •ํ™•ํ•œ Mock ์ด๋ฆ„์ง“๊ธฐ

- "MockUserRepository", "StubPatmentService"์™€ ๊ฐ™์ด ๋ช…์‹œ์ ์ธ ๋„ค์ด๋ฐ์„ ์“ฐ๋ฉด, ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ์‚ฌ๋žŒ์ด ๋ฐ”๋กœ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์ง„๋‹ค.

 


๐Ÿ“Ž Test Double ์šฉ์–ด ์ •๋ฆฌ

- Test Double๋Š” ์Šคํ„ดํŠธ ๋”๋ธ”(stunt double)์—์„œ ์ฐจ์šฉํ•œ ๊ฒƒ

- ํ…Œ์ŠคํŠธ์ค‘์ธ ์‹œ์Šคํ…œ์˜ ์ผ๋ถ€๋ถ„์ด ์™„์ „ํžˆ ์ค€๋น„๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์—์„œ ๊ทธ ๋Œ€์•ˆ์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋Š” "๊ฐ€์งœ" ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

- ํ…Œ์ŠคํŠธ ๋”๋ธ”์€ ์‹ค์ œ ๊ฐ์ฒด์˜ ํ–‰๋™์„ ๋ชจ๋ฐฉํ•˜๋ฉฐ, ํ…Œ์ŠคํŠธ๊ฐ€ ํŠน์ • ์กฐ๊ฑด๊ณผ ์ƒํ˜ธ์ž‘์šฉ์„ ์‰ฝ๊ฒŒ ์žฌํ˜„ํ•˜๊ณ  ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 

์šฉ์–ด ์ •์˜ ์„ค๋ช… ์˜ˆ์‹œ
Dummy - ์•„๋ฌด ์—ญํ• ๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
- "์ž๋ฆฌ ์ฑ„์šฐ๊ธฐ" ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ
- ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ํŠน์ • ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ํ•„์š”ํ•˜์ง€๋งŒ, ๊ทธ ์ธ์ž๋ฅผ ์‹ค์ œ๋กœ๋Š” ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ new DummyUser()๊ฐ์ฒด๋ฅผ ๋„˜๊ธฐ๊ธฐ๋งŒ ํ•˜๊ณ , ์‹ค์ œ๋ก  ํ•ด๋‹น ๊ฐ์ฒด์˜ ํ•„๋“œ๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ
Fake - ์‹ค์ œ ๊ตฌํ˜„๊ณผ ์œ ์‚ฌ
- ํ”„๋กœ๋•์…˜ ํ’ˆ์งˆ ์ˆ˜์ค€์ด ์•„๋‹Œ ๊ฐ„์ด ๊ตฌํ˜„
- ์‹ค์ œ DB ๋Œ€์‹  In-Memory DB ์‚ฌ์šฉ
- ์‹ค์ œ ์บ์‹œ ๋Œ€์‹  ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ์ž๋ฃŒ๊ตฌ์กฐ ์‚ฌ์šฉ
- Repository ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ DB๋Š” ์•„๋‹ˆ์ง€๋งŒ, DB์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ Fake๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌ
 
Stub - ํ˜ธ์ถœ์— ๋Œ€ํ•ด ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ๋‹ต๋ณ€์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •๋œ ๊ฐ์ฒด
- ํŠน์ • ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ’์„ ๊ณ ์ •
- ์™ธ๋ถ€ ์˜์กด์„ฑ์˜ ๋ณต์žกํ•œ ๋กœ์ง์„ ํ…Œ์ŠคํŠธ์—์„œ ๋ถ„๋ฆฌ
- "๊ณ ์ •๋œ ์‘๋‹ต"์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ธํŒ…ํ•  ๋•Œ
- stubRepository.findUser()ํ˜ธ์ถœ ์‹œ ํ•ญ์ƒ ํŠน์ • User ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •
Spy - Stub์˜ ๊ธฐ๋Šฅ(๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ๋‹ต๋ณ€ ๋ฐ˜ํ™˜)
+ ์–ด๋–ค ๋ฉ”์„œ๋“œ๊ฐ€ ๋ช‡๋ณ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€
+ ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ธฐ๋ก๊นŒ์ง€ ํ•จ๊ป˜ํ•ด์ฃผ๋Š” ๊ฐ์ฒด
- ์‹ค์ œ ๋กœ์ง ํ๋ฆ„์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๋ฉด์„œ, "ํ˜ธ์ถœ ์ด๋ ฅ"์„ ๊ด€์ฐฐํ•ด์•ผ ํ•  ๋•Œ - ํŠน์ • ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋˜,
์ค‘๊ฐ„์— ๋กœ๊น…/ํ˜ธ์ถœ ํšŸ์ˆ˜ ํ™•์ธ ๋“ฑ ๋ถ€์ˆ˜์  ํ™•์ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ.
Mock - ํ˜ธ์ถœ ๊ณผ์ •(ํ•จ์ˆ˜ ํ˜ธ์ถœ ํšŸ์ˆ˜, ํŒŒ๋ผ๋ฏธํ„ฐ, ์ˆœ์„œ ๋“ฑ)์— ๋Œ€ํ•œ ๊ธฐ๋Œ€์น˜๋ฅผ ๋ฏธ๋ฆฌ ์„ค์ •ํ•˜๊ณ ,
์‹ค์ œ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด
- ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ์™„์ „ํžˆ ๋Œ€์ฒดํ•˜๊ณ , ํ–‰์œ„ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ์„ ํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. - Mockito์˜ when(), verify() ๋“ฑ

 


๐Ÿงญ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์‹ค์Šต

# Given , When , Then

# assertThatThrownBy() : ์–ด๋–ค ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธ --- .isInstanceOf

# assertThat() : ๊ฐ’์ด ์–ด๋–ค ์กฐ๊ฑด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ(ํ™•์ธ ํ•ด๋ด!)

# ํ…Œ์ŠคํŠธ์ฝ”๋“œ ํด๋ž˜์Šค ์ƒ์„ฑ : Command + Shift + T


1. Controller ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

(1) @WebMvcTest(UserController.class = ํด๋ž˜์Šค๋ช…)

- Web๊ณผ ๊ด€๋ จ๋œ ์ปดํฌ๋„ŒํŠธ(@Controller, @ControllerAdvice ๋“ฑ)๋งŒ์„ ๋กœ๋“œํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑ

- Service๋‚˜ Repository ๋“ฑ์˜ ๋นˆ์€ ์ž๋™์œผ๋กœ ๋กœ๋“œ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด์˜ ๋นˆ์€ Mocking(@MockBean, @MockitoBean ๋“ฑ)ํ•˜์—ฌ ์ฃผ์ž…ํ•œ๋‹ค.

 

(2) @MockitoBean

- 3.4.0 ๋ฒ„์ „ ์ด์ „์—๋Š” @MockBean์ด ์“ฐ์˜€์ง€๋งŒ ํ˜„์žฌ๋Š” @MockitoBean์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.

- given(...).willReturn(...)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

(3) @DisplayName("...")

- ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์ข‹์€ ์„ค๋ช…์„ ๋ถ€์—ฌํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

- ํ…Œ์ŠคํŠธ ๋ฆฌํฌํŠธ๋‚˜ IDE์˜ ํ…Œ์ŠคํŠธ ํŒจ๋„์— ํ‘œ์‹œ๋˜๋ฏ€๋กœ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ์— ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

 

(4) @Autowired

- ์˜์กด์„ฑ ์ฃผ์ž…

๋”๋ณด๊ธฐ
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    UserService userService;

    @Test
    @DisplayName("์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹œ ์„ฑ๊ณต์ ์œผ๋กœ JSON ์‘๋‹ต์„ ๋ฐ˜ํ™˜")
    void getUserByEmail_shouldReturnUser() throws Exception {
        // Given: ์ค€๋น„ - ํŠน์ • ์ด๋ฉ”์ผ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •
        String email = "test@example.com";
        String name = "ํ™๊ธธ๋™";
        User user = new User(1L,name,email);
        given(userService.getUserByEmail(email)).willReturn(user);

        // When: ์•ก์…˜ - ํ•ด๋‹น ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ
        mockMvc.perform(get("/api/users/{email}", email))
                .andExpect(status().isOk())  // Then: ๊ฒฐ๊ณผ ํ™•์ธ - JSON ๊ฒฐ๊ณผ ํ™•์ธ
                .andExpect(jsonPath("$.name").value(equalTo(name)) )
                .andExpect(jsonPath("$.email").value(equalTo(email)) );
    }
}
                .andExpect(jsonPath("$.name").value(equalTo("name")) )
                .andExpect(jsonPath("$.email").value(equalTo(email)));

→ "jsonPath" : Response์— ํ•ด๋‹น ๊ฐ’์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ž‘์—…์ด๋ฏ€๋กœ Request.jsonPath(๋™์ž‘ ์•ˆํ•จ)๊ฐ€ ์•„๋‹Œ "Result.jsonPath"์œผ๋กœ import ํ•„์ˆ˜!


2. Service ํ…Œ์ŠคํŠธ

(1) @ExtendWith(MockitoExtension.class)

- MockitoExtension์€ @Mock, @InjectMocks ๋“ฑ์„ ํ•ด์„ํ•œ๋‹ค.

- Mock ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ฃผ์ž…์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

(2) @Mock

- Mockito์—์„œ Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

- when(...), thenReturn(...) ์œผ๋กœ ์ง€์ •(Stubbing)ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

(3) @InjeckMocks

- Mockingํ•œ ๋ชจ๋“  ์• ๋“ค์„ userService์™€ ์—ฐ๊ฒฐ(inject)ํ•œ๋‹ค.

 

๋”๋ณด๊ธฐ
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks		// Mockingํ•œ ๋ชจ๋“  ์• ๋“ค์„ userService์™€ ์—ฐ๊ฒฐ(inject)ํ•˜๋Š” ๊ฒƒ
    private UserService userService;

    @Test
    @DisplayName("์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹œ ํ•ด๋‹น ์‚ฌ์šฉ์ž ๋ฐ˜ํ™˜")
    void getUserByEmail_whenUserExists() {
        String email = "existing@example.com";
        User mockUser = new User(1L,"user",email);

        // Stubbing
        when(userRepository.findByEmail(email)).thenReturn(Optional.of(mockUser));

        // Service ํ˜ธ์ถœ
        User foundUser = userService.getUserByEmail(email);

        // ๊ฒ€์ฆ
        assertThat(foundUser).isNotNull();
        assertThat(foundUser.getEmail()).isEqualTo(email);
    }

    @Test
    @DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ")
    void getUserByEmail_whenUserNotExists() {
        String email = "notfound@example.com";
        when(userRepository.findByEmail(email)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> userService.getUserByEmail(email))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("User not found");
    }
}

3. Repository ํ…Œ์ŠคํŠธ

(1) @DataJpaTest

- ๊ธฐ๋ณธ์ ์œผ๋กœ ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(H2) ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ๋‹ค.

- ๋ณ„๋„์˜ DB ์„ค์ • ์—†์ด๋„ ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘๊ณผ Repository ๋™์ž‘์„ ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”๋ณด๊ธฐ
@DataJpaTest
public class UserRepositoryTest {

    @Autowired
    UserRepository userRepository;

    @Test
    @DisplayName("H2: ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ๊ฒ€์ƒ‰ ์‹œ ์กด์žฌํ•˜๋Š” ์‚ฌ์šฉ์ž ๋ฐ˜ํ™˜")
    void findByEmail_success() {
        // Given
        User saved = userRepository.save(new User("user","parksw@naver.com"));


        // When
        Optional<User> found = userRepository.findByEmail("parksw@naver.com");

        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getId()).isEqualTo(saved.getId());
        assertThat(found.get().getEmail()).isEqualTo("parksw@naver.com");
    }

    @Test
    @DisplayName("H2: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ ๊ฒ€์ƒ‰ ์‹œ ๋นˆ Optional ๋ฐ˜ํ™˜")
    void findByEmail_notFound() {
				// Given test@example.com์„ ์ฃผ์ง€ ์•Š์Œ
        // When
        Optional<User> found = userRepository.findByEmail("test@example.com");

        // Then
        assertThat(found).isEmpty();
    }
}

 

 


4. <ํ˜ผ์ž ๊ณต๋ถ€> Entity ํ…Œ์ŠคํŠธ

(1) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

- @DataJpaTest๋Š” ๊ธฐ๋ณธ์ ์€ H2๋ฅผ ์ ์šฉํ•˜๋Š”๋ฐ ๋‚˜๋Š” database๋ฅผ mysql๋กœ ์„ค์ • ํ•ด๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— 

 

(2) EntityManager - @PersistenceContext๋Š” ์ง๊ฟ์ฒ˜๋Ÿผ ๊ฐ™์ด ๋‹ค๋‹Œ๋‹ค!

- EntityManager์ด๋ž€? ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์†Œํ†ต)

- @PersistenceContext๋ž€? EntityManager๋ฅผ ์Šคํ”„๋ง์ด ์ž๋™์œผ๋กœ ์ฃผ์ž…ํ•ด์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.(์ฃผ์ž… ๋„๊ตฌ)

application.properties

...
spring.jpa.database=mysql
๋”๋ณด๊ธฐ
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ItemJPQLTest {

@PersistenceContext
private EntityManager entityManager;

// ์ƒ๋žต ...

}