이전 글에서는 MSTest 환경 설정 및 레거시 코드 리팩토링 과정을 통해 TDD(Test-Driven Development)를 적용하는 기본 전략을 다루었습니다. 이번 글에서는 외부 API 호출과 데이터베이스 접근이 포함된 코드를 Mocking하는 방법을 구체적으로 다룹니다.
- ✅ 외부 API 호출을 Moq으로 Mocking하기
- ✅ 데이터베이스 접근을 Mocking하기
- ✅ Moq 설정 및 의존성 주입 적용
🚀 1. 레거시 코드에서 외부 API와 데이터베이스 호출 문제점
기존 레거시 코드에서 다음과 같은 문제가 발생할 수 있습니다:
- ✅ 외부 API 호출이 코드에 직접 연결됨 → 테스트 불가능
- ✅ 외부 API 응답 지연 → 테스트 속도 저하
- ✅ 외부 시스템 상태에 따라 결과가 달라짐 → 테스트 결과 불안정
- ✅ 데이터베이스 상태에 따라 테스트 결과가 달라짐 → 테스트 일관성 부족
🔥 기존 레거시 코드 예제
public static class OrderService
{
public static bool ProcessOrder(int orderId)
{
var order = Database.GetOrderById(orderId);
if (order == null) return false;
var paymentResult = PaymentGateway.Charge(order.TotalAmount);
if (!paymentResult) return false;
Logger.Log($"Order {orderId} processed.");
return true;
}
}
🛠️ 2. 인터페이스 추가 및 코드 수정
외부 시스템에 대한 호출을 Mocking하기 위해 다음과 같은 순서로 코드 수정이 필요합니다:
- ✅ 외부 API 호출 → 인터페이스 정의 후 주입
- ✅ 데이터베이스 접근 → 인터페이스 정의 후 주입
- ✅ Moq을 사용해 Mocking 처리
🚀 1) 인터페이스 정의
IDatabase.cs
public interface IDatabase
{
Order GetOrderById(int orderId);
}
IPaymentGateway.cs
public interface IPaymentGateway
{
bool Charge(decimal amount);
}
🚀 2) 코드 수정
의존성을 인터페이스를 통해 주입하고, Mocking이 가능하도록 코드 수정
public class OrderService
{
private readonly IDatabase _database;
private readonly IPaymentGateway _paymentGateway;
public OrderService(IDatabase database, IPaymentGateway paymentGateway)
{
_database = database;
_paymentGateway = paymentGateway;
}
public bool ProcessOrder(int orderId)
{
var order = _database.GetOrderById(orderId);
if (order == null) return false;
var paymentResult = _paymentGateway.Charge(order.TotalAmount);
if (!paymentResult) return false;
Logger.Log($"Order {orderId} processed.");
return true;
}
}
🚀 3. 외부 API 호출 Mocking
(1) Moq 설정
Moq는 NuGet에서 설치할 수 있습니다.
Install-Package Moq
(2) Moq으로 API 호출 Mocking
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class OrderServiceTests
{
[TestMethod]
public void Test_ProcessOrder_Success()
{
// Arrange
var mockDatabase = new Mock<IDatabase>();
mockDatabase.Setup(db => db.GetOrderById(It.IsAny<int>()))
.Returns(new Order { OrderId = 1, TotalAmount = 100 });
var mockPaymentGateway = new Mock<IPaymentGateway>();
mockPaymentGateway.Setup(pg => pg.Charge(It.IsAny<decimal>()))
.Returns(true);
var service = new OrderService(mockDatabase.Object, mockPaymentGateway.Object);
// Act
var result = service.ProcessOrder(1);
// Assert
Assert.IsTrue(result);
}
}
🚀 4. 실패 케이스 추가하기
📌 실패 케이스: 결제 실패
[TestMethod]
public void Test_ProcessOrder_Failure_PaymentFailed()
{
// Arrange
var mockDatabase = new Mock<IDatabase>();
mockDatabase.Setup(db => db.GetOrderById(It.IsAny<int>()))
.Returns(new Order { OrderId = 1, TotalAmount = 100 });
var mockPaymentGateway = new Mock<IPaymentGateway>();
mockPaymentGateway.Setup(pg => pg.Charge(It.IsAny<decimal>()))
.Returns(false);
var service = new OrderService(mockDatabase.Object, mockPaymentGateway.Object);
// Act
var result = service.ProcessOrder(1);
// Assert
Assert.IsFalse(result);
}
🚀 5. 테스트 커버리지 점검
- 👉 Visual Studio → 테스트 탐색기 → 커버리지 분석 실행
- 👉 결과:
- ✅ 성공 테스트 → 초록색 표시
- ✅ 실패 테스트 → 빨간색 표시
🎯 결과
- ✅ 외부 API 및 데이터베이스 호출 Mocking 완료
- ✅ 성공 및 실패 테스트 작성 완료
- ✅ Moq을 통해 테스트 결과 제어 완료
- ✅ 코드 수정 없이 테스트 환경 제어 가능
🚀 다음 편 예고
👉 다음 글에서는 테스트 데이터베이스 설정 및 통합 테스트 전략을 다루겠습니다.
👉 통합 테스트에서 실제 데이터베이스를 사용하는 경우의 문제점과 해결책을 다룹니다.