我正在使用一些遗留代码,我需要为其编写一些单元测试。存在具有以下签名的数据访问方法。
Task ExecuteReaderAsync(string procedureName, Parameters procedureParameters,
params Action<System.Data.IDataReader>[] actions);
在我正在测试的类中有一个类似于
的实现private async Task<CustomObject> GetCustomObject(int id)
{
CustomObject obj = null;
await db.ExecuteReaderAsync("nameOfProcedure", some parameters,
dr =>
{
obj = new CustomObject()
{
Prop1 = dr["Col1"],
Prop2 = dr["Col2"]
}
}
return obj;
}
我正在努力控制GetCustomObject
返回的值。如果ExecuteReaderAsync
实际上已经返回了一些东西,我可以像这样设置。
mockDataAccess.Setup(x => x.ExecuteReaderAsync("nameOfProcedure", It.IsAny<Parameters>()))
.Returns(Task.FromResult(new CustomeObject() { prop1 = "abc", prop2 = "def"};));
但指定值的逻辑是Action<IDataReader>
,我无法控制。我想知道是否有任何技巧可以用来做我想做的事,
即控制GetCustomObject
返回的对象的值。
答案 0 :(得分:2)
看一下以下示例
[TestClass]
public class LegacyCodeTest {
[TestMethod]
public async Task TestExecuteReaderAsync() {
//Arrange
var mapping = new Dictionary<string, string> {
{ "Col1", "abc" },
{ "Col2", "def" }
};
var mockDataReader = new Mock<IDataReader>();
mockDataReader
.Setup(m => m[It.IsAny<string>()])
.Returns<string>(col => mapping[col])
.Verifiable();
var mockDataAccess = new Mock<IDataAccess>();
mockDataAccess
.Setup(m => m.ExecuteReaderAsync("nameOfProcedure", It.IsAny<Parameters>(), It.IsAny<Action<System.Data.IDataReader>[]>()))
.Returns(Task.FromResult<object>(null))
.Callback((string s, Parameters p, Action<System.Data.IDataReader>[] a) => {
if (a != null && a.Length > 0) {
a.ToList().ForEach(callback => callback(mockDataReader.Object));
}
})
.Verifiable();
var sut = new SUT(mockDataAccess.Object);
//Act
var actual = await sut.MUT(2);
//Assert
mockDataAccess.Verify();
mockDataReader.Verify(m => m["Col1"]);
mockDataReader.Verify(m => m["Col2"]);
actual.Should()
.NotBeNull()
.And
.Match<CustomObject>(c => c.Prop1 == mapping["Col1"] && c.Prop2 == mapping["Col2"]);
}
public interface IDataAccess {
Task ExecuteReaderAsync(string procedureName, Parameters procedureParameters, params Action<System.Data.IDataReader>[] actions);
}
public class Parameters { }
public class CustomObject {
public object Prop1 { get; set; }
public object Prop2 { get; set; }
}
public class SUT {
IDataAccess db;
public SUT(IDataAccess dataAccess) {
this.db = dataAccess;
}
public async Task<CustomObject> MUT(int id) {
var result = await GetCustomObject(id);
return result;
}
private async Task<CustomObject> GetCustomObject(int id) {
CustomObject obj = null;
await db.ExecuteReaderAsync("nameOfProcedure", null,
dr => {
obj = new CustomObject() {
Prop1 = dr["Col1"],
Prop2 = dr["Col2"]
};
});
return obj;
}
}
}
由于您无法控制Action<IDataReader>
,在这种情况下,可以做的最多是确保操作不会失败。所以这意味着传递一个模拟阅读器,它可以按照行动的预期执行。
var mapping = new Dictionary<string, string> {
{ "Col1", "abc" },
{ "Col2", "def" }
};
var mockDataReader = new Mock<IDataReader>();
mockDataReader
.Setup(m => m[It.IsAny<string>()])
.Returns<string>(col => mapping[col])
.Verifiable();
通过使用回调来访问传入的参数
.Callback((string s, Parameters p, Action<System.Data.IDataReader>[] a) => {
if (a != null && a.Length > 0) {
a.ToList().ForEach(callback => callback(mockDataReader.Object));
}
})
模拟读者可以传递给被测方法中调用的动作。
此答案适用于OP中提供的示例,因此可能需要对其进行一些修改才能应用于您的特定方案。这应该足以让你适应这种情况。