NimbleMock: .NET용 현대적인 초고속 모킹 라이브러리 (Moq보다 34배 빠름)
Source: Dev.to

NimbleMock를 만든 이유
수년간 엔터프라이즈 .NET 애플리케이션에서 흔히 겪는 모킹 불편함을 경험했습니다:
- Verbose setups 대형 인터페이스에 대한 설정이 장황함 (하나만 테스트하기 위해 20개 이상의 메서드를 모킹).
- Performance hits 런타임 프록시(Castle.DynamicProxy in Moq) 때문에 발생하는 성능 저하 — 큰 테스트 스위트에서 생성 및 검증이 느림.
- Static/sealed mocking hell — 유료 도구나 래퍼 없이 네이티브 지원이 없음.
- Async and generics issues — 예상치 못한 null Task 또는 어색한 타입 추론.
- Brittle tests — 로컬에서는 통과하지만 실제 API 형태와 맞지 않아 프로덕션에서 실패하는 모킹.
Moq는 검증된 라이브러리고, NSubstitute는 깔끔하고 가독성이 좋지만, 두 라이브러리 모두 소스 제너레이터 같은 최신 .NET 기능을 활용해 제로 오버헤드를 구현하지는 못합니다.
NimbleMock은 컴파일 타임 매직으로 이를 해결합니다: 런타임 프록시 없이, 공격적인 인라인 및 스택 할당을 사용합니다. 결과? MoQ 대비 34배 빠른 모크 생성과 3배 빠른 검증, 일반 시나리오에서 할당이 전혀 없습니다.
주요 기능
- Partial mocks – 필요한 부분만 모킹하고, 모킹되지 않은 메서드는
NotImplementedException을 던져 초기에 감지합니다. - Native static/sealed mocking –
DateTime.Now,Environment등을 직접 모킹합니다. - First‑class async support –
Task와ValueTask에 대한 매끄러운 지원. - Generic type inference – 중첩 제네릭(예: EF Core
IQueryable)을 완전 지원합니다. - Fluent, chainable API – NSubstitute와 Moq의 장점을 결합한 체인형 API.
- Lie‑proofing – 실제 스테이징 API와 비교해 오래된 모크 형태를 잡아내는 선택적 런타임 검증.
- Compile‑time analyzers – 오류를 조기에 포착(예: async 메서드에
SetupAsync를 빼먹은 경우 경고).
MIT‑라이선스, 오픈소스이며 기여를 환영합니다!
빠른 시작
NuGet으로 설치
dotnet add package NimbleMock
기본 예제
using NimbleMock;
public interface IUserRepository
{
User GetById(int id);
Task SaveAsync(User user);
}
var mock = Mock.Of<IUserRepository>()
.Setup(x => x.GetById(1), new User { Id = 1, Name = "Alice" })
.SetupAsync(x => x.SaveAsync(default!), true)
.Build();
var user = mock.Object.GetById(1); // Returns Alice
mock.Verify(x => x.GetById(1)).Once();
Partial mock (god‑interface에 최적)
var mock = Mock.Partial<IUserRepository>()
.Only(x => x.GetData(1), expectedData)
.Build();
// 모킹되지 않은 메서드는 안전을 위해 예외를 발생시킴
Static mocking
var staticMock = Mock.Static()
.Returns(d => d.Now, new DateTime(2025, 12, 25))
.Build();
staticMock.Verify(d => d.Now).Once();
Generics 예제
public interface IRepository
{
IQueryable<User> Query();
}
var mock = Mock.Of<IRepository>()
.Setup(x => x.Query(), users.AsQueryable())
.Build();
var results = mock.Object.Query().Where(u => u.IsActive).ToList();
성능 벤치마크
.NET 8 (BenchmarkDotNet) 환경에서 실행.
Mock Creation & Setup
| Library | Time (ns) | Memory Allocated | Gain vs Moq |
|---|---|---|---|
| Moq | 48,812 | 10.37 KB | Baseline |
| NSubstitute | 9,937 | 12.36 KB | ~5× faster |
| NimbleMock | 1,415 | 3.45 KB | 34× faster |
Method Execution Overhead
| Library | Time (µs) | Gain vs Moq |
|---|---|---|
| Moq | ~1.4 | Baseline |
| NSubstitute | ~1.6 | Slower |
| NimbleMock | ~0.6 | 2.3× faster |
Verification
| Library | Time (ns) | Memory Allocated | Gain vs Moq |
|---|---|---|---|
| Moq | 1,795 | 2.12 KB | Baseline |
| NSubstitute | 2,163 | 2.82 KB | Slower |
| NimbleMock | 585 | 0.53 KB | 3× faster |
소스 제너레이터 기반이므로 런타임 리플렉션이 전혀 없습니다!
벤치마크 직접 실행:
dotnet run --project tests/NimbleMock.Benchmarks --configuration Release
Moq/NSubstitute에서 마이그레이션
Moq에서
// Moq
var mock = new Mock<IUserRepository>();
mock.Setup(x => x.GetById(1)).Returns(user);
// NimbleMock
var mock = Mock.Of<IUserRepository>()
.Setup(x => x.GetById(1), user)
.Build();
검증도 체인형 API 덕분에 더 가독성이 좋습니다. 전체 마이그레이션 가이드는 레포지토리를 참고하세요.
Lie‑Proofing: “거짓 테스트” 방지
특징: 실제 엔드포인트와 모크를 비교 검증합니다.
var result = await LieProofing.AssertMatchesReal("https://api.staging.example.com");
if (!result.IsValid)
{
// 불일치 로그 기록
}
프로덕션 장애 전에 API 변화를 잡아냅니다.
최종 생각
NimbleMock은 .NET 8/9+ 시대를 위해 설계되었습니다: 빠르고 안전하며 즐거운 TDD. 느린 테스트 스위트나 정적 모킹 해킹에 지쳤다면 한 번 사용해 보세요!
- GitHub:
- NuGet:
피드백을 환영합니다 — 현재 모킹 라이브러리에서 겪는 불편은 무엇인가요? 네이티브 정적 모킹과 이 속도를 위해 전환하시겠습니까?
⭐️ 레포지토리에 ⭐️를 눌러 주세요, 그리고 테스트를 다시 즐겁게 만들어 봅시다! 🚀