模拟数据访问器

时间:2016-09-09 02:14:14

标签: c# unit-testing inversion-of-control moq

我正在尝试了解如何将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期望它看起来,那将是非常有用的。

2 个答案:

答案 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

以下是有关Mock Matching Arguments

的更多信息