3

我正在使用 Moq 库进行单元测试。现在我想要的是,当我第一次访问我的对象时,它应该返回 null,而当我第二次访问它时,它应该返回其他内容。

这是我的代码

var mock = new Mock<IMyClass>();
mock.Setup(?????);
mock.Setup(?????);

var actual = target.Method(mock.object);

在我的方法中,我首先检查模拟对象是否为空,如果为空,则对其进行初始化,然后对其进行一些调用。

bool Method(IMyClass myObj)
{
    if (myObj != null)
        return true;
    else
    {
        myObj = new MyClass();
        bool result = myObj.SomeFunctionReturningBool();
        return result;
    }
}

为模拟对象做些什么设置,

我还需要知道如何模拟这条线

bool result = myObj.SomeFunctionReturningBool();
4

5 回答 5

2

听起来您正在尝试使用一种测试方法运行两个测试 - 也许将测试分成两个更好?

如果方法被传递为 null,您还想初始化一个新对象。为了测试这一点,我建议创建一个工厂对象,负责创建MyClass. 新代码如下所示:

interface IMyClassFactory
{
    IMyClass CreateMyClass();
}

bool Method(IMyClass myObj, IMyClassFactory myClassFactory)
{
    if (myObj != null)
    {
        return true;
    }

    myObj = myClassFactory.CreateMyClass();
    return myObj.SomeFunctionReturningBool();
}

然后测试看起来像:

[Test]
public void Method_ShouldReturnTrueIfNotPassedNull()
{
    Assert.That(target.Method(new MyClass()), Is.True);
}

[Test]
public void Method_ShouldCreateObjectAndReturnResultOfSomeFunctionIfPassedNull()
{
    // Arrange
    bool expectedResult = false;

    var mockMyClass = new Mock<IMyClass>();
    mockMyClass.Setup(x => x.SomeFunctionReturningBool()).Returns(expectedResult);

    var mockMyFactory = new Mock<IMyClassFactory>();
    mockMyFactory.Setup(x => x.CreateMyClass()).Returns(mockMyClass.Object);

    // Act
    var result = target.Method(null, mockMyFactory.Object);

    // Assert
    mockMyClass.Verify(x => x.SomeFunctionReturningBool(), Times.Once());
    mockMyFactory.Verify(x => x.CreateMyClass(), Times.Once());
    Assert.That(result, Is.EqualTo(expectedResult));
}

这里工厂模式被用来传入一个可以创建IMyClass类型对象的对象,然后工厂本身就被模拟了。

如果您不想更改方法的签名,请在类的构造函数中创建工厂,并通过类的公共属性使其可访问。然后它可以在模拟工厂的测试中被覆盖。这称为依赖注入

于 2012-07-16T10:43:52.570 回答
1

Moq - 返回 null - 这个工作示例简单地说明了如何使用 Moq 返回 null。虽然需要的代码行是下面的注释行,但下面提供了一个完整的工作示例。

// _mockShopService.Setup(x => x.GetProduct(It.IsAny<string>())).Returns(() => null);

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
}

public interface IShopService
{
    Product GetProduct(string productId);
}

public class ShopService : IShopService
{
    public Product GetProduct(string productId)
    {
        if (string.IsNullOrWhiteSpace(productId))
        {
            return new Product();
        }

        return new Product { Id = "8160807887984", Name = "How to return null in Moq" };
    }
}

public class Shop
{
    private static IShopService _shopService;

    public Shop(IShopService shopService)
    {
        _shopService = shopService;
    }

    public Product GetProduct(string productId)
    {
        Product product = _shopService.GetProduct(productId);

        return product;
    }
}

[TestClass]
public class ShopTests
{
    Mock<IShopService> _mockShopService;

    [TestInitialize]
    public void Setup()
    {
        _mockShopService = new Mock<IShopService>();
    }

    [TestMethod]
    public void ShopService_GetProduct_Returns_null()
    {
        //Arrange
        Shop shop = new Shop(_mockShopService.Object);

        //This is how we return null --- all other code above is to bring this line of code home
        _mockShopService.Setup(x => x.GetProduct(It.IsAny<string>())).Returns(() => null);

        //Act
        var actual = shop.GetProduct(It.IsAny<string>());

        //Assert

        Assert.IsNull(actual);
    }
}
于 2017-11-20T14:00:26.607 回答
0

目前,您的 SUT 与MyClass实现紧密耦合。您不能模拟new在 SUT 中使用关键字实例化的对象。因此,您不能单独测试您的 SUT,并且您的测试不再是单元测试。当实现 ofMyClass.SomeFunctionReturningBool将发生变化(它将返回true而不是false),您的 SUT 测试将失败。这不应该发生。因此,将创建委托给某个依赖项(工厂)并将该依赖项注入您的 SUT:

[Test]
public void ShouldReturnTrueWhenMyClassIsNotNull()
{
    Mock<IMyClassFactory> factory = new Mock<IMyClassFactory>();
    Mock<IMyClass> myClass = new Mock<IMyClass>();
    var foo = new Foo(factory.Object);
    Assert.True(foo.Method(myClass.Object));
}

[Test]
public void ShouldCreateNewMyClassAndReturnSomeFunctionValue()
{
    bool expected = true;
    Mock<IMyClass> myClass = new Mock<IMyClass>();
    myClass.Setup(mc => mc.SomeFunctionReturningBool()).Returns(expected);
    Mock<IMyClassFactory> factory = new Mock<IMyClassFactory>();
    factory.Setup(f => f.CreateMyClass()).Returns(myClass.Object);

    var foo = new Foo(factory.Object);

    Assert.That(foo.Method(null), Is.EqualTo(expected));
    factory.VerifyAll();
    myClass.VerifyAll();
}

顺便说一句,为方法参数分配新值不会影响您传递给方法的引用。

执行:

public class Foo
{
    private IMyClassFactory _factory;

    public Foo(IMyClassFactory factory)
    {
        _factory = factory;
    }

    public bool Method(IMyClass myObj)
    {
        if (myObj != null)
            return true;       

        return _factory.CreateMyClass().SomeFunctionReturningBool();
    }
}
于 2012-07-16T11:48:13.850 回答
0

要模拟结果值,您可以简单地执行以下操作:

mock.Setup(foo => foo.SomeFunctionReturningBool()).Returns(true); // or false :)

对于另一个问题,只需在单元测试中通过 null 而不是通过mock.object,您的单元测试也涵盖了这一点。所以你基本上创建了两个单元测试:

var actual = target.Method(mock.object);

另一个是:

var actual = target.Method(null);
于 2012-07-16T10:39:48.213 回答
0

您可以使用带有参数的 TestFixture。此测试将运行两次和不同的类型值。

using NUnit.Framework;

namespace Project.Tests
{
    [TestFixture(1)] 
    [TestFixture(2)]
    public class MyTest
    {
        private int _intType;

         public MyTest(int type)
         {
             _intType = type;
         }

        [SetUp]
        public void Setup()
        {
            if (_intType==1)
            {
                //Mock Return false
            }
            else
            {
                //Mock Return Value
            }
        }
    }
}
于 2012-07-16T10:50:58.993 回答