Skip to content

Commit 4744e97

Browse files
author
Kevin McManus
committed
Created basic readme documentation with supporting code files
1 parent 158ed95 commit 4744e97

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

README.md

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,97 @@
11
NSubstituteAutoMocker
22
=====================
33

4-
Details will follow.
4+
Put very simply NSubstitueAutoMocker is a very lightweight extension to the NSubstitute mocking framework. Whilst there may be other uses, its primary function is to further simplify unit testing and create a class under test, whose own constructor may contain dependencies that you wish to mock.
5+
6+
The problem
7+
-----------
8+
9+
Imagine the following class needed some testing (yes, arguably the tests should come first if your following true TDD):
10+
11+
12+
class SavingsAccount
13+
{
14+
private readonly IInterestCalculator _interestCalculator;
15+
16+
public SavingsAccount(IInterestCalculator interestCalculator)
17+
{
18+
_interestCalculator = interestCalculator;
19+
}
20+
21+
public decimal Balance { get; private set; }
22+
23+
public void Deposit(decimal amount)
24+
{
25+
Balance += amount;
26+
}
27+
28+
public void WithDraw(decimal amount)
29+
{
30+
Balance -= amount;
31+
}
32+
33+
public void ApplyInterest()
34+
{
35+
Balance += _interestCalculator.Calculate();
36+
}
37+
}
38+
39+
interface IInterestCalculator
40+
{
41+
decimal Calculate();
42+
}
43+
44+
45+
A typical unit test using the NSubstitute mocking framework might look something similar to that below:
46+
47+
[TestClass]
48+
public class SavingsAccountTests
49+
{
50+
[TestMethod]
51+
public void ApplyInterestUpdatesTheBalance()
52+
{
53+
// Arange
54+
IInterestCalculator interestCalculator = Substitute.For<IInterestCalculator>();
55+
interestCalculator.Calculate().Returns(123);
56+
SavingsAccount savingsAccount = new SavingsAccount(interestCalculator);
57+
58+
// Act
59+
savingsAccount.ApplyInterest();
60+
61+
// Assert
62+
Assert.AreEqual(123, savingsAccount.Balance);
63+
}
64+
}
65+
66+
Using NSubstituteAutoMocker the test can be simplifed to the following:
67+
68+
[TestClass]
69+
public class SavingsAccountTestsWithNSubstituteAutoMocker
70+
{
71+
[TestMethod]
72+
public void ApplyInterestUpdatesTheBalance()
73+
{
74+
// Arange
75+
var automocker = new NSubstituteAutoMocker<SavingsAccount>();
76+
automocker.Get<IInterestCalculator>().Calculate().Returns(123);
77+
78+
// Act
79+
automocker.ClassUnderTest.ApplyInterest();
80+
81+
// Assert
82+
Assert.AreEqual(123, automocker.ClassUnderTest.Balance);
83+
}
84+
}
85+
86+
The key difference is that constructor dependency has not been explicitly defined. Whilst this might not seem significant (we've only reduced our line count be one), imagine the scenario of a class with a large constructor parameter count. Not only would this result in additional code in the first test above, it will also create a maintainance nightmare when a developer changes the signature (e.g. the addition of a new argument). Such a change would result in the update of all tests on the class, for which there may be many. This would be true even for tests that don't need the dependency. As a direct consequence of using the AutoMocker, the test code becomes significantly easier to write and also maintain, focusing the developer on the functionality being testest and not of the wiring of test objects.
87+
88+
As the parameters were not explicitly defined in the test, their access mechanism is through the Get<T>() method, where T is the type of the parameter you are requesting. In the case of multiple parameters of the same type, an optional string parameter can be used to specify the parameter name.
89+
90+
Under the hood the automocker simply makes a call out to NSubstitute for each of the parameters it finds on the constructor. In the event that the class under test contains multiple constructors, the constructor to the automocker allows you to supply a type list of arguments. When using the default constructor, the automockers behavoir is to locate the class with the highest number of parameters (or the first items it finds in the result of a conflict).
91+
92+
There may be scenarios where you want to override the autogenerated parameters. This might be to inject something new, or to supply additional properties that are not initialised by default. This can be achieved through the Func<> parameter on the automockers constructor. Please be mindful that this only allows you to override the constructor parameters to the class under test, there is no mechanism to override dependency graphs (if you find yourself needing to do this, it could be a testability/design code smell too).
93+
94+
Final note
95+
----------
96+
97+
This extension has been built with unit testing in mind. Paying tribute to NSubstitute it has been designed to keep things simple. There are much more powerfull automockers that make use of Dependency Injection containers, this is not one of them.

source/NSubstituteAutoMocker.UnitTests/NSubstituteAutoMocker.UnitTests.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
<WarningLevel>4</WarningLevel>
3636
</PropertyGroup>
3737
<ItemGroup>
38+
<Reference Include="NSubstitute, Version=1.5.0.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
39+
<SpecificVersion>False</SpecificVersion>
40+
<HintPath>..\packages\NSubstitute.1.5.0.0\lib\NET40\NSubstitute.dll</HintPath>
41+
</Reference>
3842
<Reference Include="System" />
3943
</ItemGroup>
4044
<Choose>
@@ -50,6 +54,7 @@
5054
</Otherwise>
5155
</Choose>
5256
<ItemGroup>
57+
<Compile Include="DocumentationSnippets\FirstExample.cs" />
5358
<Compile Include="NSubstituteAutoMockerTests.cs" />
5459
<Compile Include="Properties\AssemblyInfo.cs" />
5560
<Compile Include="SamplesToTest\ClassWithJustDefaultConstructor.cs" />
@@ -70,6 +75,9 @@
7075
<Name>NSubstituteAutoMocker</Name>
7176
</ProjectReference>
7277
</ItemGroup>
78+
<ItemGroup>
79+
<None Include="packages.config" />
80+
</ItemGroup>
7381
<Choose>
7482
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
7583
<ItemGroup>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<packages>
3+
<package id="NSubstitute" version="1.5.0.0" targetFramework="net45" />
4+
</packages>

0 commit comments

Comments
 (0)