我正在尝试了解如何将Moq与NUnit和IoC一起使用。
(我在BitBucket中有我的完整项目,但不知道如何分享它......) https://bitbucket.org/Cralis/skeleton/overview
我有一个逻辑方法(登录)我正在尝试测试。它需要一个请求对象(具有用户名,密码和IP地址)。如果用户名和/或密码为空,则逻辑返回失败状态,并且不会转到数据访问层。
所以我正在创建一个单元测试来测试它。 (这是我第一次尝试嘲笑......)
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);
}
这在断言上失败了,因为&#39;结果&#39;是NULL。
我可能做错了很多。例如,我不确定为什么我需要在顶部设置请求和响应对象,但因为我找到的所有示例都是&#39; string&#39;和&#39; int&#39;输入,我似乎无法使用It.IsAny ......
有人可以帮我理解吗?在断言中获取NULL的错误是什么?我单步执行,代码按预期执行。但结果是null,因为我从未调用过数据访问器(它使用了模拟)。
编辑: 啊,
// Set it's return value
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response);
解决了这个问题。我不知道为什么,所以如果你可以帮助我理解和重构这个,以便它作为一个经验丰富的Moq / UnitTester期望它看起来,那将是非常有用的。
答案 0 :(得分:3)
即使您的request
对象具有与传递给var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" });
相同的属性值,它们也是不同的对象,因此您尝试使用{{1}返回的值没有回来。
更改
MockedDataAccess.Setup(x => x.Login(request)).Returns(response);
到
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);
调用其参数的任何值时,返回{{1 }}“
关于问题的第二部分,您需要设置请求和响应对象的原因是默认情况下,您在模拟对象上调用的任何方法都将返回null。下面列出的MockedDataAccess.Login
方法会返回response
的值。由于BusinessLogic.Login
是模拟,dataAccess.Login()
方法将返回null,除非您另有说明。
dataAccess
你说你认为你做错了很多,但是你设置测试的方式就像我做的那样。我唯一要改变的是(除了修复上面描述的dataAccess.Login()
方法之外)是使用 UnitOfWork_StateUnderTest_ExpectedBehavior 命名模式进行测试。例如 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);
}
答案 1 :(得分:2)
此代码的问题
var result = logic.Login(new LoginRequest { Password = "dummy", Username = "dummy", IPAddress = "1.1.1.1" })
是在Login
方法的实现中,这称为
dataAccess.Login(request)
这意味着你必须为方法DataAccess
设置Login
的模拟,因为mock没有其他任何东西。模拟如果假并需要设置,以便它按您需要的方式工作。在这种情况下,@ Ben Rubin的答案绝对正确。
当像这样设置mock时
MockedDataAccess.Setup(x => x.Login(request)).Returns(response)
然后有必要使用与设置数据访问Login
方法时使用的请求完全相同的请求对象来调用测试中的方法,因为否则mock将起到不设置的作用。在这里你基本上说',当用这个请求调用DataAccess Login时,将返回该响应'。
但是当像这样设置模拟时
MockedDataAccess.Setup(x => x.Login(It.IsAny<LoginRequest>())).Returns(response)
然后它可以工作,因为此处Login
设置为任何LoginRequest
。因此,无论使用什么请求,模拟都将返回response
。 HTH