模拟数据访问器
Mocking out the Data Accessor
我正在尝试学习如何将 Moq 与 NUnit 和 IoC 结合使用。
(我在 BitBucket 中有我的完整项目,但不确定如何共享它...)
https://bitbucket.org/Cralis/skeleton/overview
我有一个逻辑方法(登录)正在尝试测试。它需要一个请求对象(具有用户名、密码和 IP 地址)。如果用户名and/or密码为空,逻辑returns失败状态,不进入数据访问层。
所以我正在创建一个单元测试来测试它。
(这是我第一次尝试模拟...)
public void NotNull_Returns_True()
{
// Arrange
var request = new LoginRequest { IPAddress = "1.1.1.1", Username = "dummy", Password = "dummy" };
var response = new LoginResponse { Request = request, Success = true, Message = "", UserID = 1 };
// Setup the moc data accessor, as we don't want to gop to the concrete one.
var MockedDataAccess = new Mock<IDataAccess>();
// Set it's return value
MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
// Instantiate the Logic class we're testing, using a Moc data accessor.
var logic = new BusinessLogic(MockedDataAccess.Object);
// Act
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
// Assert
Assert.AreEqual(true, result.Success);
}
断言失败,因为 'result' 为 NULL。
我可能做错了很多。例如,我不确定为什么我需要在顶部设置请求和响应对象,但是因为我找到的所有示例都是 'string' 和 'int' 输入,所以我似乎无法使用It.IsAny...
有人可以帮助我理解这里吗?我在断言中得到 NULL 做错了什么?我单步执行,代码按预期执行。但是结果是空的,因为我从来没有调用数据访问器(它使用了模拟)。
编辑:
啊,
// Set it's return value
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
问题已解决。我不确定为什么,所以如果你能帮助我理解和重构它,使其看起来像一个有经验的 Moq/UnitTester 所期望的那样,那将非常有用。
即使您的 request
对象具有与您传递给 var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
相同的 属性 值,但它们是不同的对象,因此您尝试传递的值return 和 MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
没有得到 returned。
改变
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
到
var result = logic.Login(request);
它与 MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
一起工作的原因是因为现在你说 "when MockedDataAccess.Login
is called with any value for its parameter, return response
"
关于问题的第二部分,您需要设置请求和响应对象的原因是默认情况下,您在模拟对象上调用的任何方法都将 return 为 null。下面列出的 BusinessLogic.Login
方法将 return dataAccess.Login()
的值。由于 dataAccess
是一个 mock,因此 dataAccess.Login()
方法将 return 为空,除非您另有说明。
public LoginResponse Login(LoginRequest request)
{
// Basic validation
if (request == null)
return new LoginResponse
{
Success = false,
Message = "Empty Request"
};
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))
return new LoginResponse
{
Success = false,
Message = "Username and/or password empty"
};
// This is returning null since dataAccess is a mock
return dataAccess.Login(request);
}
你说你认为你做错了很多,但你设置测试的方式与我所做的差不多。我唯一要更改的(除了如上所述修复 Setup
方法之外)是为您的测试使用 UnitOfWork_StateUnderTest_ExpectedBehavior 命名模式。例如Login_ValidLoginRequest_ShouldReturnValidLoginResponse()
这段代码的问题
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" })
是在Login
方法的实现中这个调用
dataAccess.Login(request)
这意味着您必须为方法 Login
设置 DataAccess
的 mock,因为 mock 否则什么都不做。 Mock if fake 并且需要设置以便它按照你需要的方式工作。在这种情况下,@Ben Rubin 的回答是绝对正确的。
像这样设置模拟时
MockedDataAccess.Setup(x => x.Login(request)).Returns(response)
然后有必要使用与数据访问 Login
方法设置中使用的请求完全相同的请求对象来调用被测方法,否则 mock 将充当未设置。在这里你基本上说 'when DataAccess Login is called with exactly this request, that response will be returned'.
但是当像这样设置 mock 时
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response)
然后就可以了,因为这里 Login
设置为任何 LoginRequest
。所以在这种情况下,无论使用什么请求,模拟都会 return response
。 HTH
这里有更多关于Mock Matching Arguments
的信息
我正在尝试学习如何将 Moq 与 NUnit 和 IoC 结合使用。
(我在 BitBucket 中有我的完整项目,但不确定如何共享它...) https://bitbucket.org/Cralis/skeleton/overview
我有一个逻辑方法(登录)正在尝试测试。它需要一个请求对象(具有用户名、密码和 IP 地址)。如果用户名and/or密码为空,逻辑returns失败状态,不进入数据访问层。
所以我正在创建一个单元测试来测试它。 (这是我第一次尝试模拟...)
public void NotNull_Returns_True()
{
// Arrange
var request = new LoginRequest { IPAddress = "1.1.1.1", Username = "dummy", Password = "dummy" };
var response = new LoginResponse { Request = request, Success = true, Message = "", UserID = 1 };
// Setup the moc data accessor, as we don't want to gop to the concrete one.
var MockedDataAccess = new Mock<IDataAccess>();
// Set it's return value
MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
// Instantiate the Logic class we're testing, using a Moc data accessor.
var logic = new BusinessLogic(MockedDataAccess.Object);
// Act
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
// Assert
Assert.AreEqual(true, result.Success);
}
断言失败,因为 'result' 为 NULL。
我可能做错了很多。例如,我不确定为什么我需要在顶部设置请求和响应对象,但是因为我找到的所有示例都是 'string' 和 'int' 输入,所以我似乎无法使用It.IsAny...
有人可以帮助我理解这里吗?我在断言中得到 NULL 做错了什么?我单步执行,代码按预期执行。但是结果是空的,因为我从来没有调用数据访问器(它使用了模拟)。
编辑: 啊,
// Set it's return value
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
问题已解决。我不确定为什么,所以如果你能帮助我理解和重构它,使其看起来像一个有经验的 Moq/UnitTester 所期望的那样,那将非常有用。
即使您的 request
对象具有与您传递给 var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
相同的 属性 值,但它们是不同的对象,因此您尝试传递的值return 和 MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
没有得到 returned。
改变
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
到
var result = logic.Login(request);
它与 MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
一起工作的原因是因为现在你说 "when MockedDataAccess.Login
is called with any value for its parameter, return response
"
关于问题的第二部分,您需要设置请求和响应对象的原因是默认情况下,您在模拟对象上调用的任何方法都将 return 为 null。下面列出的 BusinessLogic.Login
方法将 return dataAccess.Login()
的值。由于 dataAccess
是一个 mock,因此 dataAccess.Login()
方法将 return 为空,除非您另有说明。
public LoginResponse Login(LoginRequest request)
{
// Basic validation
if (request == null)
return new LoginResponse
{
Success = false,
Message = "Empty Request"
};
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))
return new LoginResponse
{
Success = false,
Message = "Username and/or password empty"
};
// This is returning null since dataAccess is a mock
return dataAccess.Login(request);
}
你说你认为你做错了很多,但你设置测试的方式与我所做的差不多。我唯一要更改的(除了如上所述修复 Setup
方法之外)是为您的测试使用 UnitOfWork_StateUnderTest_ExpectedBehavior 命名模式。例如Login_ValidLoginRequest_ShouldReturnValidLoginResponse()
这段代码的问题
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" })
是在Login
方法的实现中这个调用
dataAccess.Login(request)
这意味着您必须为方法 Login
设置 DataAccess
的 mock,因为 mock 否则什么都不做。 Mock if fake 并且需要设置以便它按照你需要的方式工作。在这种情况下,@Ben Rubin 的回答是绝对正确的。
像这样设置模拟时
MockedDataAccess.Setup(x => x.Login(request)).Returns(response)
然后有必要使用与数据访问 Login
方法设置中使用的请求完全相同的请求对象来调用被测方法,否则 mock 将充当未设置。在这里你基本上说 'when DataAccess Login is called with exactly this request, that response will be returned'.
但是当像这样设置 mock 时
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response)
然后就可以了,因为这里 Login
设置为任何 LoginRequest
。所以在这种情况下,无论使用什么请求,模拟都会 return response
。 HTH
这里有更多关于Mock Matching Arguments
的信息