模拟数据访问器

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

的信息