简体   繁体   中英

Unit Testing a Business Logic Interface

Am I going about this the right way? And if so, am I understanding it correctly? I am a little confused.

My project is setup in three different layers. The UI Layer, the Business Layer, and the Data Access Layer. The Business Layer and Data Access Layer are both built off of interfaces.

I am attempting to write Unit Tests using NUnit and Moq.

Here is my example. I want to test GetSum(int x, int y), which is a simple function that just returns x + y. This function exists in CalculatorLogic and implements ICalculatorLogic.

public class CalculatorLogic : ICalculatorLogic
{
    public int GetSum(int x, int y)
    {
        return x + y;
    }
}

Now, here is how I am attempting to write a UnitTest.

[TestFixture]
public class CalculatorLogicTests
{
    Mock<ICalculatorLogic> calculatorLogicMock;
    ICalculatorLogic calculatorLogic;

    public CalculatorLogicTests()
    {
        calculatorLogicMock = new Mock<ICalculatorLogic>();

        // now i need to do this setup, right?
        calculatorLogicMock.Setup(x => x.GetSum(It.IsAny<int>(), It.IsAny<int>())).Returns(6);

        calculatorLogic = calculatorLogicMock.Object;
    }

    [Test]
    public void GetSum_Test()
    {
        int expectedResult = 3 + 3;

        var sum = calculatorLogic.GetSum(3, 3);
        Assert.AreEqual(sum, expectedResult);
    }
}

Now, the above passes. It runs, and it gets what I am expecting. However, it feels wrong. It's just returning whatever I set it to return in the Setup() call. If I had put a 3 in the Returns() instead of the 6, it would fail.

I must be understanding something wrong. Otherwise, am I really testing my function if I'm telling it what to return?

Okay so you've got an interface ICalculatorLogic and an implementation of said interface CalculatorLogic that you wish to write tests for.

You're using a mocking framework Moq to mock dependencies that your CalculatorLogic does not have. You should write something like this:

[TestFixture]
public class CalculatorLogicTests
{
    // Our unit under test.
    ICalculatorLogic calculatorLogic;

    [SetUp]
    public void SetUp()
    {
        // This method is called for every [Test] in this class.
        // So let's recreate our CalculatorLogic here so that each
        // test has a fresh instance.
        calculatorLogic = new CalculatorLogic();
    }

    [Test]
    public void GetSum_WithTwoIntegers_ReturnsTheirSum()
    {
        // Arrange
        int expectedResult = 3 + 3;

        // Act
        var sum = calculatorLogic.GetSum(3, 3);

        // Assert
        Assert.AreEqual(sum, expectedResult);
    }
}

Now let's say you want your GetSum to log the parameters that it is called with. You might create a logger interface like so:

public interface ILogger {
    void Log(int x, int y);
}

You then put it as a dependency by requiring it in the constructor of your CalculatorLogic class:

public class CalculatorLogic : ICalculatorLogic
{
    private readonly ILogger logger;

    // Now we have a dependency on ILogger!
    public CalculatorLogic(ILogger l) {
        logger = l;
    }

    public int GetSum(int x, int y)
    {
        // Let's log those numbers!
        logger.Log(x, y);
        return x + y;
    }
} 

Then you can write your tests like so (using Moq ):

[TestFixture]
public class CalculatorLogicTests
{
    // This guy we want to test.
    ICalculatorLogic calculatorLogic;

    // Our mock!
    Mock<ILogger> loggerMock;

    [SetUp]
    public void SetUp()
    {
        // Create the logger mock!
        loggerMock = new Mock<ILogger>();

        // Inject the logger into our CalculatorLogic!
        calculatorLogic = new CalculatorLogic(loggerMock.Object);
    }

    [Test]
    public void GetSum_WithTwoIntegers_ShouldCallLogger()
    {
        // Arrange
        int expectedResult = 3 + 3;

        // Act
        var sum = calculatorLogic.GetSum(3, 3);

        // Assert
        Assert.AreEqual(sum, expectedResult);

        // Verify that the logger's Log method was called once with x = 3 and y = 3.
        loggerMock.Verify(logger => logger.Log(It.Is<int>(x => x == 3), It.Is<int>(y => y == 3)), Times.Once());
    }
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM