[{"data":1,"prerenderedAt":977},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-mocking":3},{"id":4,"title":5,"body":6,"description":962,"difficulty":963,"extension":964,"framework":965,"frameworkSlug":966,"meta":967,"navigation":106,"order":73,"path":968,"qaPath":969,"seo":970,"stem":971,"subtopic":972,"topic":973,"topicSlug":974,"updated":975,"__hash__":976},"blog\u002Fblog\u002Fdotnet-mocking.md","Mocking in .NET with Moq and NSubstitute",{"type":7,"value":8,"toc":945},"minimark",[9,14,32,36,44,54,181,184,188,191,279,286,290,305,310,400,404,424,428,481,489,493,523,526,530,548,563,570,574,636,646,650,653,702,713,717,720,803,806,810,820,891,905,909,912,932,935,941],[10,11,13],"h2",{"id":12},"why-mocking-comes-up-in-every-net-testing-interview","Why mocking comes up in every .NET testing interview",[15,16,17,18,22,23,26,27,31],"p",{},"Mocking is the technique that makes unit testing possible for real applications.\nWithout it, testing a ",[19,20,21],"code",{},"PaymentService"," would require a live payment gateway; testing\nan ",[19,24,25],{},"OrderRepository"," would require a database. Interviewers probe mocking to assess\nwhether a candidate understands ",[28,29,30],"em",{},"when"," to mock (not everything!), how to configure\nmocks correctly, and the difference between stubs and interaction verification.",[10,33,35],{"id":34},"what-a-mock-is-and-what-it-is-not","What a mock is — and what it is not",[15,37,38,39,43],{},"A ",[40,41,42],"strong",{},"mock"," is a controllable substitute for a real dependency that:",[45,46,47,51],"ol",{},[48,49,50],"li",{},"Returns predetermined values so you can test the unit in isolation",[48,52,53],{},"Records method calls so you can verify interactions after the fact",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F Real dependency — hits SMTP in every test:\npublic class OrderService\n{\n    private readonly SmtpEmailSender _emailer = new(); \u002F\u002F tight coupling\n    public void PlaceOrder(Order o) => _emailer.Send(o.Email, \"Confirmed!\");\n}\n\n\u002F\u002F Mockable — dependency injected through an interface:\npublic class OrderService\n{\n    private readonly IEmailSender _emailer;\n    public OrderService(IEmailSender emailer) => _emailer = emailer;\n    public void PlaceOrder(Order o) => _emailer.Send(o.Email, \"Confirmed!\");\n}\n\n\u002F\u002F In tests:\nvar mockEmailer = new Mock\u003CIEmailSender>();\nvar service     = new OrderService(mockEmailer.Object);\nservice.PlaceOrder(new Order { Email = \"a@b.com\" });\nmockEmailer.Verify(e => e.Send(\"a@b.com\", \"Confirmed!\"), Times.Once);\n","csharp","",[19,62,63,71,77,83,89,95,101,108,114,119,124,130,136,141,146,151,157,163,169,175],{"__ignoreMap":60},[64,65,68],"span",{"class":66,"line":67},"line",1,[64,69,70],{},"\u002F\u002F Real dependency — hits SMTP in every test:\n",[64,72,74],{"class":66,"line":73},2,[64,75,76],{},"public class OrderService\n",[64,78,80],{"class":66,"line":79},3,[64,81,82],{},"{\n",[64,84,86],{"class":66,"line":85},4,[64,87,88],{},"    private readonly SmtpEmailSender _emailer = new(); \u002F\u002F tight coupling\n",[64,90,92],{"class":66,"line":91},5,[64,93,94],{},"    public void PlaceOrder(Order o) => _emailer.Send(o.Email, \"Confirmed!\");\n",[64,96,98],{"class":66,"line":97},6,[64,99,100],{},"}\n",[64,102,104],{"class":66,"line":103},7,[64,105,107],{"emptyLinePlaceholder":106},true,"\n",[64,109,111],{"class":66,"line":110},8,[64,112,113],{},"\u002F\u002F Mockable — dependency injected through an interface:\n",[64,115,117],{"class":66,"line":116},9,[64,118,76],{},[64,120,122],{"class":66,"line":121},10,[64,123,82],{},[64,125,127],{"class":66,"line":126},11,[64,128,129],{},"    private readonly IEmailSender _emailer;\n",[64,131,133],{"class":66,"line":132},12,[64,134,135],{},"    public OrderService(IEmailSender emailer) => _emailer = emailer;\n",[64,137,139],{"class":66,"line":138},13,[64,140,94],{},[64,142,144],{"class":66,"line":143},14,[64,145,100],{},[64,147,149],{"class":66,"line":148},15,[64,150,107],{"emptyLinePlaceholder":106},[64,152,154],{"class":66,"line":153},16,[64,155,156],{},"\u002F\u002F In tests:\n",[64,158,160],{"class":66,"line":159},17,[64,161,162],{},"var mockEmailer = new Mock\u003CIEmailSender>();\n",[64,164,166],{"class":66,"line":165},18,[64,167,168],{},"var service     = new OrderService(mockEmailer.Object);\n",[64,170,172],{"class":66,"line":171},19,[64,173,174],{},"service.PlaceOrder(new Order { Email = \"a@b.com\" });\n",[64,176,178],{"class":66,"line":177},20,[64,179,180],{},"mockEmailer.Verify(e => e.Send(\"a@b.com\", \"Confirmed!\"), Times.Once);\n",[15,182,183],{},"The interface is the seam. Without it, there is nothing to replace.",[10,185,187],{"id":186},"the-five-types-of-test-doubles","The five types of test doubles",[15,189,190],{},"The word \"mock\" is used loosely, but there are actually five distinct types:",[192,193,194,210],"table",{},[195,196,197],"thead",{},[198,199,200,204,207],"tr",{},[201,202,203],"th",{},"Type",[201,205,206],{},"Does",[201,208,209],{},"When to use",[211,212,213,227,240,253,266],"tbody",{},[198,214,215,221,224],{},[216,217,218],"td",{},[40,219,220],{},"Dummy",[216,222,223],{},"Passed but never used",[216,225,226],{},"Fill required constructor parameters",[198,228,229,234,237],{},[216,230,231],{},[40,232,233],{},"Stub",[216,235,236],{},"Returns fixed values",[216,238,239],{},"Control return values without verifying calls",[198,241,242,247,250],{},[216,243,244],{},[40,245,246],{},"Mock",[216,248,249],{},"Verifies interactions",[216,251,252],{},"Assert that a side effect occurred",[198,254,255,260,263],{},[216,256,257],{},[40,258,259],{},"Spy",[216,261,262],{},"Records calls for later inspection",[216,264,265],{},"Capture arguments passed to a method",[198,267,268,273,276],{},[216,269,270],{},[40,271,272],{},"Fake",[216,274,275],{},"Working simplified implementation",[216,277,278],{},"Complex interactions too hard to stub",[15,280,281,282,285],{},"In practice, Moq creates stubs and mocks (and spies via ",[19,283,284],{},"Callback","). Fakes are\nusually hand-written.",[10,287,289],{"id":288},"moq-the-net-mocking-standard","Moq: the .NET mocking standard",[15,291,292,293,296,297,300,301,304],{},"Moq is the most widely used mocking library in .NET. The core API is three methods:\n",[19,294,295],{},"Setup",", ",[19,298,299],{},"Returns",", and ",[19,302,303],{},"Verify",".",[306,307,309],"h3",{"id":308},"configuring-return-values","Configuring return values",[55,311,313],{"className":57,"code":312,"language":59,"meta":60,"style":60},"var mockRepo = new Mock\u003CIProductRepository>();\n\n\u002F\u002F Return a specific value for exact arguments:\nmockRepo.Setup(r => r.GetById(42)).Returns(new Product { Id = 42 });\n\n\u002F\u002F Return for any argument:\nmockRepo.Setup(r => r.GetById(It.IsAny\u003Cint>()))\n        .Returns(new Product { Id = 1 });\n\n\u002F\u002F Return based on the argument value:\nmockRepo.Setup(r => r.GetById(It.Is\u003Cint>(id => id > 0)))\n        .Returns\u003Cint>(id => new Product { Id = id });\n\n\u002F\u002F Throw an exception:\nmockRepo.Setup(r => r.GetById(-1)).Throws\u003CArgumentOutOfRangeException>();\n\n\u002F\u002F Async — use ReturnsAsync:\nmockRepo.Setup(r => r.GetByIdAsync(42)).ReturnsAsync(new Product { Id = 42 });\n",[19,314,315,320,324,329,334,338,343,348,353,357,362,367,372,376,381,386,390,395],{"__ignoreMap":60},[64,316,317],{"class":66,"line":67},[64,318,319],{},"var mockRepo = new Mock\u003CIProductRepository>();\n",[64,321,322],{"class":66,"line":73},[64,323,107],{"emptyLinePlaceholder":106},[64,325,326],{"class":66,"line":79},[64,327,328],{},"\u002F\u002F Return a specific value for exact arguments:\n",[64,330,331],{"class":66,"line":85},[64,332,333],{},"mockRepo.Setup(r => r.GetById(42)).Returns(new Product { Id = 42 });\n",[64,335,336],{"class":66,"line":91},[64,337,107],{"emptyLinePlaceholder":106},[64,339,340],{"class":66,"line":97},[64,341,342],{},"\u002F\u002F Return for any argument:\n",[64,344,345],{"class":66,"line":103},[64,346,347],{},"mockRepo.Setup(r => r.GetById(It.IsAny\u003Cint>()))\n",[64,349,350],{"class":66,"line":110},[64,351,352],{},"        .Returns(new Product { Id = 1 });\n",[64,354,355],{"class":66,"line":116},[64,356,107],{"emptyLinePlaceholder":106},[64,358,359],{"class":66,"line":121},[64,360,361],{},"\u002F\u002F Return based on the argument value:\n",[64,363,364],{"class":66,"line":126},[64,365,366],{},"mockRepo.Setup(r => r.GetById(It.Is\u003Cint>(id => id > 0)))\n",[64,368,369],{"class":66,"line":132},[64,370,371],{},"        .Returns\u003Cint>(id => new Product { Id = id });\n",[64,373,374],{"class":66,"line":138},[64,375,107],{"emptyLinePlaceholder":106},[64,377,378],{"class":66,"line":143},[64,379,380],{},"\u002F\u002F Throw an exception:\n",[64,382,383],{"class":66,"line":148},[64,384,385],{},"mockRepo.Setup(r => r.GetById(-1)).Throws\u003CArgumentOutOfRangeException>();\n",[64,387,388],{"class":66,"line":153},[64,389,107],{"emptyLinePlaceholder":106},[64,391,392],{"class":66,"line":159},[64,393,394],{},"\u002F\u002F Async — use ReturnsAsync:\n",[64,396,397],{"class":66,"line":165},[64,398,399],{},"mockRepo.Setup(r => r.GetByIdAsync(42)).ReturnsAsync(new Product { Id = 42 });\n",[306,401,403],{"id":402},"verifying-interactions","Verifying interactions",[55,405,407],{"className":57,"code":406,"language":59,"meta":60,"style":60},"mockEmailer.Verify(e => e.Send(\"a@b.com\", It.IsAny\u003Cstring>()), Times.Once);\nmockEmailer.Verify(e => e.Send(It.IsAny\u003Cstring>(), It.IsAny\u003Cstring>()), Times.Never);\nmockEmailer.VerifyNoOtherCalls(); \u002F\u002F fail if any unexpected calls occurred\n",[19,408,409,414,419],{"__ignoreMap":60},[64,410,411],{"class":66,"line":67},[64,412,413],{},"mockEmailer.Verify(e => e.Send(\"a@b.com\", It.IsAny\u003Cstring>()), Times.Once);\n",[64,415,416],{"class":66,"line":73},[64,417,418],{},"mockEmailer.Verify(e => e.Send(It.IsAny\u003Cstring>(), It.IsAny\u003Cstring>()), Times.Never);\n",[64,420,421],{"class":66,"line":79},[64,422,423],{},"mockEmailer.VerifyNoOtherCalls(); \u002F\u002F fail if any unexpected calls occurred\n",[306,425,427],{"id":426},"capturing-arguments-with-callback","Capturing arguments with Callback",[55,429,431],{"className":57,"code":430,"language":59,"meta":60,"style":60},"var savedOrders = new List\u003COrder>();\nmockRepo.Setup(r => r.Save(It.IsAny\u003COrder>()))\n        .Callback\u003COrder>(o => savedOrders.Add(o))\n        .Returns(true);\n\nservice.PlaceOrder(new Order { Sku = \"X\" });\nservice.PlaceOrder(new Order { Sku = \"Y\" });\n\nAssert.Equal(2, savedOrders.Count);\nAssert.Equal(\"X\", savedOrders[0].Sku);\n",[19,432,433,438,443,448,453,457,462,467,471,476],{"__ignoreMap":60},[64,434,435],{"class":66,"line":67},[64,436,437],{},"var savedOrders = new List\u003COrder>();\n",[64,439,440],{"class":66,"line":73},[64,441,442],{},"mockRepo.Setup(r => r.Save(It.IsAny\u003COrder>()))\n",[64,444,445],{"class":66,"line":79},[64,446,447],{},"        .Callback\u003COrder>(o => savedOrders.Add(o))\n",[64,449,450],{"class":66,"line":85},[64,451,452],{},"        .Returns(true);\n",[64,454,455],{"class":66,"line":91},[64,456,107],{"emptyLinePlaceholder":106},[64,458,459],{"class":66,"line":97},[64,460,461],{},"service.PlaceOrder(new Order { Sku = \"X\" });\n",[64,463,464],{"class":66,"line":103},[64,465,466],{},"service.PlaceOrder(new Order { Sku = \"Y\" });\n",[64,468,469],{"class":66,"line":110},[64,470,107],{"emptyLinePlaceholder":106},[64,472,473],{"class":66,"line":116},[64,474,475],{},"Assert.Equal(2, savedOrders.Count);\n",[64,477,478],{"class":66,"line":121},[64,479,480],{},"Assert.Equal(\"X\", savedOrders[0].Sku);\n",[15,482,483,485,486,488],{},[19,484,284],{}," is more powerful than ",[19,487,303],{}," for asserting on complex argument state\n— it captures the full object for later inspection.",[306,490,492],{"id":491},"sequential-return-values","Sequential return values",[55,494,496],{"className":57,"code":495,"language":59,"meta":60,"style":60},"\u002F\u002F SetupSequence — different return per call:\nmockHttp.SetupSequence(c => c.GetAsync(\"\u002Fapi\u002Fdata\"))\n        .Returns(Task.FromResult((string?)null))          \u002F\u002F 1st call\n        .Returns(Task.FromResult(\"{ \\\"ok\\\": true }\"))     \u002F\u002F 2nd call\n        .Throws\u003CHttpRequestException>();                   \u002F\u002F 3rd call\n",[19,497,498,503,508,513,518],{"__ignoreMap":60},[64,499,500],{"class":66,"line":67},[64,501,502],{},"\u002F\u002F SetupSequence — different return per call:\n",[64,504,505],{"class":66,"line":73},[64,506,507],{},"mockHttp.SetupSequence(c => c.GetAsync(\"\u002Fapi\u002Fdata\"))\n",[64,509,510],{"class":66,"line":79},[64,511,512],{},"        .Returns(Task.FromResult((string?)null))          \u002F\u002F 1st call\n",[64,514,515],{"class":66,"line":85},[64,516,517],{},"        .Returns(Task.FromResult(\"{ \\\"ok\\\": true }\"))     \u002F\u002F 2nd call\n",[64,519,520],{"class":66,"line":91},[64,521,522],{},"        .Throws\u003CHttpRequestException>();                   \u002F\u002F 3rd call\n",[15,524,525],{},"This is essential for testing retry logic or stateful workflows.",[10,527,529],{"id":528},"strict-vs-loose-mocks","Strict vs loose mocks",[15,531,532,535,536,539,540,543,544,547],{},[40,533,534],{},"Loose"," (the default) returns ",[19,537,538],{},"default(T)"," for any unconfigured call.\n",[40,541,542],{},"Strict"," throws ",[19,545,546],{},"MockException"," for any unconfigured call.",[55,549,551],{"className":57,"code":550,"language":59,"meta":60,"style":60},"var looseMock  = new Mock\u003CIProductRepository>();              \u002F\u002F unconfigured → null\nvar strictMock = new Mock\u003CIProductRepository>(MockBehavior.Strict); \u002F\u002F unconfigured → exception\n",[19,552,553,558],{"__ignoreMap":60},[64,554,555],{"class":66,"line":67},[64,556,557],{},"var looseMock  = new Mock\u003CIProductRepository>();              \u002F\u002F unconfigured → null\n",[64,559,560],{"class":66,"line":73},[64,561,562],{},"var strictMock = new Mock\u003CIProductRepository>(MockBehavior.Strict); \u002F\u002F unconfigured → exception\n",[15,564,565,566,569],{},"Most teams stay with loose mocks and use ",[19,567,568],{},"VerifyNoOtherCalls()"," when they need\nstrict enforcement on a specific test. Global strict behaviour is brittle —\nadding a logging call to production code breaks every strict mock that didn't\nset it up.",[10,571,573],{"id":572},"argument-matchers","Argument matchers",[55,575,577],{"className":57,"code":576,"language":59,"meta":60,"style":60},"\u002F\u002F Any value of the type:\nmock.Setup(r => r.Save(It.IsAny\u003COrder>())).Returns(true);\n\n\u002F\u002F Value satisfying a predicate:\nmock.Setup(r => r.Save(It.Is\u003COrder>(o => o.Total > 0))).Returns(true);\n\n\u002F\u002F Not null:\nmock.Setup(r => r.Save(It.IsNotNull\u003COrder>())).Returns(true);\n\n\u002F\u002F Value in a set:\nmock.Setup(r => r.GetByStatus(It.IsIn(OrderStatus.Pending, OrderStatus.Processing)))\n    .Returns(new List\u003COrder>());\n",[19,578,579,584,589,593,598,603,607,612,617,621,626,631],{"__ignoreMap":60},[64,580,581],{"class":66,"line":67},[64,582,583],{},"\u002F\u002F Any value of the type:\n",[64,585,586],{"class":66,"line":73},[64,587,588],{},"mock.Setup(r => r.Save(It.IsAny\u003COrder>())).Returns(true);\n",[64,590,591],{"class":66,"line":79},[64,592,107],{"emptyLinePlaceholder":106},[64,594,595],{"class":66,"line":85},[64,596,597],{},"\u002F\u002F Value satisfying a predicate:\n",[64,599,600],{"class":66,"line":91},[64,601,602],{},"mock.Setup(r => r.Save(It.Is\u003COrder>(o => o.Total > 0))).Returns(true);\n",[64,604,605],{"class":66,"line":97},[64,606,107],{"emptyLinePlaceholder":106},[64,608,609],{"class":66,"line":103},[64,610,611],{},"\u002F\u002F Not null:\n",[64,613,614],{"class":66,"line":110},[64,615,616],{},"mock.Setup(r => r.Save(It.IsNotNull\u003COrder>())).Returns(true);\n",[64,618,619],{"class":66,"line":116},[64,620,107],{"emptyLinePlaceholder":106},[64,622,623],{"class":66,"line":121},[64,624,625],{},"\u002F\u002F Value in a set:\n",[64,627,628],{"class":66,"line":126},[64,629,630],{},"mock.Setup(r => r.GetByStatus(It.IsIn(OrderStatus.Pending, OrderStatus.Processing)))\n",[64,632,633],{"class":66,"line":132},[64,634,635],{},"    .Returns(new List\u003COrder>());\n",[15,637,638,641,642,645],{},[40,639,640],{},"Constraint",": if you use a matcher for one argument, all arguments in that call\nmust use matchers too. You cannot mix ",[19,643,644],{},"It.IsAny\u003Cint>()"," with a bare literal.",[10,647,649],{"id":648},"nsubstitute-an-alternative-syntax","NSubstitute: an alternative syntax",[15,651,652],{},"NSubstitute removes the lambda-wrapping and reads closer to plain C#:",[55,654,656],{"className":57,"code":655,"language":59,"meta":60,"style":60},"\u002F\u002F Moq:\nvar mock = new Mock\u003CICalculator>();\nmock.Setup(c => c.Add(2, 3)).Returns(5);\nmock.Verify(c => c.Add(2, 3), Times.Once);\n\n\u002F\u002F NSubstitute:\nvar sub = Substitute.For\u003CICalculator>();\nsub.Add(2, 3).Returns(5);\nsub.Received(1).Add(2, 3); \u002F\u002F verify after the fact\n",[19,657,658,663,668,673,678,682,687,692,697],{"__ignoreMap":60},[64,659,660],{"class":66,"line":67},[64,661,662],{},"\u002F\u002F Moq:\n",[64,664,665],{"class":66,"line":73},[64,666,667],{},"var mock = new Mock\u003CICalculator>();\n",[64,669,670],{"class":66,"line":79},[64,671,672],{},"mock.Setup(c => c.Add(2, 3)).Returns(5);\n",[64,674,675],{"class":66,"line":85},[64,676,677],{},"mock.Verify(c => c.Add(2, 3), Times.Once);\n",[64,679,680],{"class":66,"line":91},[64,681,107],{"emptyLinePlaceholder":106},[64,683,684],{"class":66,"line":97},[64,685,686],{},"\u002F\u002F NSubstitute:\n",[64,688,689],{"class":66,"line":103},[64,690,691],{},"var sub = Substitute.For\u003CICalculator>();\n",[64,693,694],{"class":66,"line":110},[64,695,696],{},"sub.Add(2, 3).Returns(5);\n",[64,698,699],{"class":66,"line":116},[64,700,701],{},"sub.Received(1).Add(2, 3); \u002F\u002F verify after the fact\n",[15,703,704,705,708,709,712],{},"Both integrate with xUnit and NUnit equally well. NSubstitute has no strict mode;\nuse ",[19,706,707],{},"DidNotReceive()"," and ",[19,710,711],{},"ReceivedCalls()"," for inspection. The choice is mostly\nteam preference.",[10,714,716],{"id":715},"the-most-common-mocking-mistake-over-mocking","The most common mocking mistake: over-mocking",[15,718,719],{},"Over-mocking means replacing real collaborators with mocks even when the real\nimplementation is fast, deterministic, and side-effect-free. The result is tests\nthat break on every refactor even when behavior is unchanged.",[55,721,723],{"className":57,"code":722,"language":59,"meta":60,"style":60},"\u002F\u002F Over-mocked — tests the call graph, not the behavior:\nvar mockValidator = new Mock\u003CIOrderValidator>();\nvar mockPricer    = new Mock\u003CIPricingEngine>();\nvar mockAudit     = new Mock\u003CIAuditLogger>();\n\u002F\u002F ... configure all three ...\n\u002F\u002F ... verify all three were called ...\n\n\u002F\u002F Better — only mock what crosses a process boundary:\nvar mockEmailer = new Mock\u003CIEmailSender>();  \u002F\u002F side effect: real email\nvar mockRepo    = new Mock\u003CIOrderRepository>(); \u002F\u002F side effect: real DB\n\nvar service = new OrderService(\n    new OrderValidator(),   \u002F\u002F real — fast, no side effects\n    mockRepo.Object,\n    new PricingEngine(),    \u002F\u002F real — pure calculation\n    mockEmailer.Object);\n",[19,724,725,730,735,740,745,750,755,759,764,769,774,778,783,788,793,798],{"__ignoreMap":60},[64,726,727],{"class":66,"line":67},[64,728,729],{},"\u002F\u002F Over-mocked — tests the call graph, not the behavior:\n",[64,731,732],{"class":66,"line":73},[64,733,734],{},"var mockValidator = new Mock\u003CIOrderValidator>();\n",[64,736,737],{"class":66,"line":79},[64,738,739],{},"var mockPricer    = new Mock\u003CIPricingEngine>();\n",[64,741,742],{"class":66,"line":85},[64,743,744],{},"var mockAudit     = new Mock\u003CIAuditLogger>();\n",[64,746,747],{"class":66,"line":91},[64,748,749],{},"\u002F\u002F ... configure all three ...\n",[64,751,752],{"class":66,"line":97},[64,753,754],{},"\u002F\u002F ... verify all three were called ...\n",[64,756,757],{"class":66,"line":103},[64,758,107],{"emptyLinePlaceholder":106},[64,760,761],{"class":66,"line":110},[64,762,763],{},"\u002F\u002F Better — only mock what crosses a process boundary:\n",[64,765,766],{"class":66,"line":116},[64,767,768],{},"var mockEmailer = new Mock\u003CIEmailSender>();  \u002F\u002F side effect: real email\n",[64,770,771],{"class":66,"line":121},[64,772,773],{},"var mockRepo    = new Mock\u003CIOrderRepository>(); \u002F\u002F side effect: real DB\n",[64,775,776],{"class":66,"line":126},[64,777,107],{"emptyLinePlaceholder":106},[64,779,780],{"class":66,"line":132},[64,781,782],{},"var service = new OrderService(\n",[64,784,785],{"class":66,"line":138},[64,786,787],{},"    new OrderValidator(),   \u002F\u002F real — fast, no side effects\n",[64,789,790],{"class":66,"line":143},[64,791,792],{},"    mockRepo.Object,\n",[64,794,795],{"class":66,"line":148},[64,796,797],{},"    new PricingEngine(),    \u002F\u002F real — pure calculation\n",[64,799,800],{"class":66,"line":153},[64,801,802],{},"    mockEmailer.Object);\n",[15,804,805],{},"A useful question: \"If I replace this dependency with a mock, does the test run\nfaster or more reliably?\" If the answer is no — if the real implementation is\njust a pure function or a simple value object — use the real one.",[10,807,809],{"id":808},"mocking-httpclient","Mocking HttpClient",[15,811,812,815,816,819],{},[19,813,814],{},"HttpClient"," is a concrete class with no interface, but its ",[19,817,818],{},"HttpMessageHandler","\nis virtual. Swap the handler:",[55,821,823],{"className":57,"code":822,"language":59,"meta":60,"style":60},"public class MockHttpMessageHandler : HttpMessageHandler\n{\n    private readonly HttpResponseMessage _response;\n    public MockHttpMessageHandler(HttpResponseMessage r) => _response = r;\n\n    protected override Task\u003CHttpResponseMessage> SendAsync(\n        HttpRequestMessage req, CancellationToken ct)\n        => Task.FromResult(_response);\n}\n\nvar handler    = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK)\n    { Content = new StringContent(\"\"\"{\"id\":1}\"\"\", Encoding.UTF8, \"application\u002Fjson\") });\nvar httpClient = new HttpClient(handler) { BaseAddress = new Uri(\"https:\u002F\u002Fapi.example.com\") };\nvar service    = new ProductService(httpClient);\n",[19,824,825,830,834,839,844,848,853,858,863,867,871,876,881,886],{"__ignoreMap":60},[64,826,827],{"class":66,"line":67},[64,828,829],{},"public class MockHttpMessageHandler : HttpMessageHandler\n",[64,831,832],{"class":66,"line":73},[64,833,82],{},[64,835,836],{"class":66,"line":79},[64,837,838],{},"    private readonly HttpResponseMessage _response;\n",[64,840,841],{"class":66,"line":85},[64,842,843],{},"    public MockHttpMessageHandler(HttpResponseMessage r) => _response = r;\n",[64,845,846],{"class":66,"line":91},[64,847,107],{"emptyLinePlaceholder":106},[64,849,850],{"class":66,"line":97},[64,851,852],{},"    protected override Task\u003CHttpResponseMessage> SendAsync(\n",[64,854,855],{"class":66,"line":103},[64,856,857],{},"        HttpRequestMessage req, CancellationToken ct)\n",[64,859,860],{"class":66,"line":110},[64,861,862],{},"        => Task.FromResult(_response);\n",[64,864,865],{"class":66,"line":116},[64,866,100],{},[64,868,869],{"class":66,"line":121},[64,870,107],{"emptyLinePlaceholder":106},[64,872,873],{"class":66,"line":126},[64,874,875],{},"var handler    = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK)\n",[64,877,878],{"class":66,"line":132},[64,879,880],{},"    { Content = new StringContent(\"\"\"{\"id\":1}\"\"\", Encoding.UTF8, \"application\u002Fjson\") });\n",[64,882,883],{"class":66,"line":138},[64,884,885],{},"var httpClient = new HttpClient(handler) { BaseAddress = new Uri(\"https:\u002F\u002Fapi.example.com\") };\n",[64,887,888],{"class":66,"line":143},[64,889,890],{},"var service    = new ProductService(httpClient);\n",[15,892,893,894,896,897,900,901,904],{},"Always inject ",[19,895,814],{}," or ",[19,898,899],{},"IHttpClientFactory"," — never ",[19,902,903],{},"new HttpClient()"," inside\na class, which makes it impossible to test.",[10,906,908],{"id":907},"read-only-property-mocking","Read-only property mocking",[15,910,911],{},"Moq can mock get-only properties on interfaces or virtual properties on classes:",[55,913,915],{"className":57,"code":914,"language":59,"meta":60,"style":60},"var mockCtx = new Mock\u003CIUserContext>();\nmockCtx.Setup(c => c.UserId).Returns(\"user-42\");\nmockCtx.Setup(c => c.IsAdmin).Returns(true);\n",[19,916,917,922,927],{"__ignoreMap":60},[64,918,919],{"class":66,"line":67},[64,920,921],{},"var mockCtx = new Mock\u003CIUserContext>();\n",[64,923,924],{"class":66,"line":73},[64,925,926],{},"mockCtx.Setup(c => c.UserId).Returns(\"user-42\");\n",[64,928,929],{"class":66,"line":79},[64,930,931],{},"mockCtx.Setup(c => c.IsAdmin).Returns(true);\n",[15,933,934],{},"If a property is concrete and non-virtual, you cannot mock it — extract an interface.",[15,936,937,940],{},[40,938,939],{},"Rule of thumb:"," Mock collaborators at the boundary between your code and the outside\nworld (databases, email, HTTP, time). Use real implementations for same-process logic.\nIf a mock makes a test harder to read without making it faster or more reliable,\nthe mock is doing more harm than good.",[942,943,944],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":60,"searchDepth":73,"depth":73,"links":946},[947,948,949,950,956,957,958,959,960,961],{"id":12,"depth":73,"text":13},{"id":34,"depth":73,"text":35},{"id":186,"depth":73,"text":187},{"id":288,"depth":73,"text":289,"children":951},[952,953,954,955],{"id":308,"depth":79,"text":309},{"id":402,"depth":79,"text":403},{"id":426,"depth":79,"text":427},{"id":491,"depth":79,"text":492},{"id":528,"depth":73,"text":529},{"id":572,"depth":73,"text":573},{"id":648,"depth":73,"text":649},{"id":715,"depth":73,"text":716},{"id":808,"depth":73,"text":809},{"id":907,"depth":73,"text":908},"How to use Moq and NSubstitute to isolate dependencies in .NET tests — Setup, Verify, argument matchers, strict vs loose behavior, and the over-mocking mistake that makes tests brittle.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-mocking","\u002Fdotnet\u002Ftesting\u002Fmocking",{"title":5,"description":962},"blog\u002Fdotnet-mocking","Mocking","Testing","testing","2026-06-23","h9BvtSGL5kya3jEeef0K6XPnTFfNuZQmWuG2XdfYR_U",1782244086032]