[C#.NET] 單元測試 - 如何使用 NSubstitute 模擬被測物件與其它物件互動
很多時候被測目標沒有回傳值(viod 方法),若想要驗証被測方法是否有做了哪些動作(調用方法),Received() 擴充方法加上被測方法,
Note:
有關 NSubstitue 的用法:
- 模擬物件 ICalculator 實體化:Substitute.For<ICalculator>()
- 驗証被測目標有調用某方法1次:.Received(1)
- 驗証被測目標無調用某方法:.DidNotReceive()
- 驗証被測目標有調用某方法1次,忽略參數驗証:.ReceivedWithAnyArgs(1)
- 驗証被測目標無調用某方法,忽略參數驗驗証:.DidNotReceiveWithAnyArgs()
有關隔離技巧 Stub、Mock、Fake:
準備 ICalculator 和 Command
public interface ICalculator
{
int Add(int first, int senond);
int Subtract(int first, int senond);
event EventHandler Executed;
string Mode { get; set; }
}
public class Command
{
private ICalculator calculator;
public bool Called { get; set; }
public Command(ICalculator calculator)
{
this.calculator = calculator;
calculator.Executed += command_Executed;
}
void command_Executed(object sender, EventArgs e)
{
this.Called = true;
}
public void Do()
{
calculator.Add(1, 3);
if (string.IsNullOrWhiteSpace(calculator.Mode))
{
calculator.Mode = "Add";
}
}
public void NotDo()
{
}
}
驗証是否有調用某方法幾次並忽略參數範圍:
- Received(2).Add(1,1) 表示是否調用 Add 方法 2 次,且 Add 方法兩個參數均為1
- Received() 不帶參數,但至少驗証是否調用一次
- 參數使用請參考:http://www.dotblogs.com.tw/yc421206/2015/02/17/149507
public void CheckCallReceivedCount_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
command.Do();
//Assert
mock.Received(1).Add(Arg.Any<int>(), Arg.Any<int>());
}
驗証是否有調用某方法幾次並驗証參數範圍:
public void CheckCallArgAndReceivedCount_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
command.Do();
//Assert
mock.Received(1).Add(Arg.Is(1), Arg.Is<int>(x => x > 2));
}
驗証是否沒調用某方法並忽略參數範圍:
- DidNotReceive()方法用來驗証是否無調用
public void CheckNoCallReceivedCount_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
command.NotDo();
//Assert
mock.DidNotReceive().Add(Arg.Any<int>(), Arg.Any<int>());
}
忽略參數:
- 忽略參數,驗証是否有調用某方法: .ReceivedWithAnyArgs(),這跟 Received(1).Add(Arg.Any<int>(), Arg.Any<int>()) 一樣
- 忽略參數,驗証是否無調用某方法: .DidNotReceiveWithAnyArgs(),這跟mock.DidNotReceive().Add(Arg.Any<int>(), Arg.Any<int>())一樣
public void Test_CheckReceivedCalls_IgnoringArguments()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
command.Do();
//Assert
mock.ReceivedWithAnyArgs().Add(2, 2);
mock.DidNotReceiveWithAnyArgs().Subtract(1, 3);
}
驗証觸發事件後的結果:
- Raise.Event()方法,用來引發事件
public void CheckEventSubscription_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
mock.Executed += Raise.Event();
//Assert
Assert.IsTrue(command.Called);
}
驗証事件是否有被觸發:
- 不建議使用此方法,應該是測事件引發後的結果,而不是事件的實現方式
public void CheckCallEventSubscription_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//assert
mock.Received().Executed += command.OnExecuted;
mock.Received().Executed += Arg.Any<EventHandler>();
}
驗証是否有存取屬性:
- 驗証是否調用屬性的 getter:var temp = mock.Received().Mode,需要特地指派給一個臨時變數
- 驗証是否調用屬性的 setter,參數為Add:mock.Received().Mode = "Add";
public void CheckCallPropety_Test()
{
//Arrange
var mock = Substitute.For<ICalculator>();
var command = new Command(mock);
//Act
command.Do();
//驗証是否調用屬性的getter
var temp = mock.Received().Mode;
//驗証是否調用屬性的setter,參數為Add
mock.Received().Mode = "Add";
}
驗証索引器:
public void Test_CheckReceivedCalls_CheckingCallsToIndexers()
{
var dictionary = Substitute.For<IDictionary<string, int>>();
dictionary["test"] = 1;
dictionary.Received()["test"] = 1;
dictionary.Received()["test"] = Arg.Is<int>(x => x < 2);
}
文章出自:https://www.dotblogs.com.tw/yc421206/2015/02/17/149512
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET