C# 使用 NSubstitute 模擬物件執行測試

在單元測試中通常只會測試一個最小單元,例如一個方法(method)。而在程式中通常一個功能會需要其他功能搭配,很難只測試到一個單元。為了達成只測試一個單元,我們會將這個單元所有依賴可能會變動的部分使用模擬的方式確保每次執行的環境都一樣。
而 NSubstitute 是一個語法簡潔的物件模擬框架,非常適合用來幫助我們進行單元測試。

安裝

先使用 NuGet 安裝 NSubstitute 套件,或是使用 .NET CLI 執行以下指令安裝
	
dotnet add package NSubstitute
    

使用 NSubstitute 模擬

假設我們有個使用者的儲存庫介面,實際使用時會繼承此介面實現從資料庫中讀取使用者資料:
    
public interface IUserRepository
{
    UserEntity Find(int id);
    int Add(UserEntity dto);
}
    

使用者實體:
    
public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}
    

要模擬非常的簡單:
    
IUserRepository userRepository = Substitute.For<IUserRepository>(); // 建立假物件
userRepository.Find(1).Returns(new UserEntity { Id = 1, Name = "小明" }); // 指定 Find 1 時的回應內容

UserEntity user = userRepository.Find(1); // 呼叫 Find 1
Console.WriteLine($"Id:{user.Id}, Name:{user.Name}"); // Id:1, Name:小明
    

上面只有模擬 Find(1) ,如果是其他的例如 Find(2) 就會找不到內容:
    
var userEntity = userRepository.Find(2);
Console.WriteLine($"Id:{userEntity.Id}, Name:{userEntity.Name}"); // 拋出例外: System.NullReferenceException: Object reference not set to an instance of an object.
    

不過我們也可以使用 ReturnsForAnyArgs 來指定不論什麼參數都會回應的內容:
    
IUserRepository userRepository = Substitute.For<IUserRepository>(); // 建立假物件
userRepository.Find(1).ReturnsForAnyArgs(new UserEntity { Id = 1, Name = "小明" }); // 指定 Find 時的回應內容

UserEntity user = userRepository.Find(1); // 呼叫 Find 1
Console.WriteLine($"Id:{user.Id}, Name:{user.Name}"); // Id:1, Name:小明


var userEntity = userRepository.Find(2);
Console.WriteLine($"Id:{userEntity.Id}, Name:{userEntity.Name}"); // Id:1, Name:小明
    

也支援依照不同的順序回傳不同的內容,下面的範例中全部都是呼叫 Find(1) ,但是透過模擬讓每次回應的內容都不一樣:
    
IUserRepository userRepository = Substitute.For<IUserRepository>(); // 建立假物件
userRepository.Find(1).Returns(
    new UserEntity { Id = 1, Name = "小明" },
    new UserEntity { Id = 2, Name = "小王" },
    new UserEntity { Id = 3, Name = "小李" }
);

UserEntity user1 = userRepository.Find(1); // 呼叫 Find 1
Console.WriteLine($"Id:{user1.Id}, Name:{user1.Name}"); // Id:1, Name:小明

UserEntity user2 = userRepository.Find(1); // 呼叫 Find 1
Console.WriteLine($"Id:{user2.Id}, Name:{user2.Name}"); // Id:2, Name:小王

UserEntity user3 = userRepository.Find(1); // 呼叫 Find 1
Console.WriteLine($"Id:{user3.Id}, Name:{user3.Name}"); // Id:3, Name:小李
    



參考資料:
NSubstitute
GitHub - nsubstitute/NSubstitute

留言