简体   繁体   English

如何在单元测试中使用Moq调用同一类中的另一个方法

[英]How to use Moq in unit test that calls another method in same class

Hi I am new to Moq framework and have some questions about how to use it. 嗨,我是Moq框架的新手,对如何使用它有一些疑问。 I will give an example and hope for answers. 我将举一个例子并希望得到答案。

I have two classes, an interface and and an implementation: 我有两个类,一个接口和一个实现:

public class Vehicle{
   public string RegistrationNumber {get; set;}
   public long VehicleIdentifier { get; set; }
   public Tyre TyreSpecification { get; set; }
}

public class Tyre {
    public long NumberOfTyres {get; set;}
    public long TyreSize { get; set;}
}

public interface ISelecter {
   Vehicle GetVehicleByRegistrationNumber(string registrationNumber);
   Tyre GetTyreSpecification(long vehicleIdentifier);
}

public class Selecter : ISelecter
{
    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber)
    {
        var vehicle = 'Database will give us the vehicle specification';

        //Then we do things with the vehicle object

        //Get the tyre specification
        vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier);

        return vehicle;

    }

    public Tyre GetTyreSpecification(long vehicleIdentifier)
    {
         var tyre = 'external manufacture system gets the tyre specification';

         //Then do thing with the tyre before returning the object


         return tyre;
    }
}

I want to write two tests for those methods. 我想为这些方法编写两个测试。 The problem is when I write the test for GetVehicleByRegistrationNumber I do not know how to mock the method call to GetTyreSpecification . 问题是当我为GetVehicleByRegistrationNumber编写测试时,我不知道如何模拟对GetTyreSpecification的方法调用。

The test methods look like this: 测试方法如下所示:

[TestClass]
public class SelecterTest
{
    [TestMethod]
    public void GetTyreSpecification_test()
    {
        //Arrange
        var tyre = new Tyre { NumberOfTyres = 4, TyreSize = 18 };

        var mockSelecter = new Mock<ISelecter>();
        mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre);

        //Act
        var tyreSpec = mockSelecter.Object.GetTyreSpecification(123456);

        //Assert
        Assert.IsTrue(tyreSpec.NumberOfTyres == 4 && tyreSpec.TyreSize == 18);
    }

    [TestMethod]
    public void GetVehicleByRegistrationNumber_test()
    {
        //Arrange
        var vehicle= new Vehicle { VehicleIdentifier = 123456, RegistrationNumber = ABC123, TyreSpecification = new Tyre { Tyresize = 18, NumberOfTyres = 4 }};

        var mockSelecter = new Mock<ISelecter>();
        mockSelecter.SetUp(s=>s.GetVehicleByRegistrationNumber(It.IsAny<string>     ())).Returns(vehicle);

        //Act
        var vehicle = mockSelecter.Object.GetVehicleByregistrationNumber(123456);

        //Assert
        Assert.IsTrue(vehicle.Registrationnumber == "ABC123";
    }
}

In the test method GetVehicleByRegistrationNumber_test how do I mock the call to getTyreSpecification ? 在测试方法GetVehicleByRegistrationNumber_test如何模拟对getTyreSpecification的调用?

You shouldn't be trying to mock a method on the class you're trying to test. 你不应该试图在你试图测试的类上模拟一个方法。 Mocking frameworks are used to replace the actual calls made to dependencies that your class takes in with fake calls so that you can focus on testing the behaviour of your class without being distracted by external dependencies that it has. 模拟框架用于替换对您的类使用虚假调用所接受的依赖项的实际调用,以便您可以专注于测试类的行为,而不会被它具有的外部依赖性分散注意力。

There are no external dependencies taken in by your Selecter class so you don't need to mock anything. 您的Selecter类没有外部依赖项,因此您无需模拟任何内容。 I would always advocate not mocking if you don't have to and testing the actual code itself. 如果你不需要并且测试实际的代码本身,我总是提倡不要嘲笑。 Obviously, to keep your test atomic, you would need to mock calls to external dependencies if there were any. 显然,为了保持测试的原子性,如果有的话,你需要模拟对外部依赖的调用。

The focus on mocking the class under test has blinded you to the actual problem. 嘲笑被测试阶级的重点使你对实际问题视而不见。

From the comments in the class under test... 来自被测课程的评论......

  • 'Database will give us the vehicle specification' '数据库将为我们提供车辆规格'
  • 'external manufacture system gets the tyre specification' '外部制造系统获得轮胎规格'

you actually expose two dependencies that should be injected into the class. 你实际上暴露了应该注入到类中的两个依赖项。

For the purpose of explaining this answer lets say those dependencies looked like this. 为了解释这个答案,我们可以说这些依赖关系看起来像这样。

public interface IDatabase {
    Vehicle GetVehicleByRegistrationNumber(string registrationNumber);
}

public interface IExternalManufactureSystem {
    Tyre GetTyreSpecification(long vehicleIdentifier);
}

That would mean that the Selecter would need to be refactored to expect those dependencies. 这意味着需要对Selecter进行重构以期望这些依赖关系。

public class Selecter : ISelecter {
    private IDatabase database;
    private IExternalManufactureSystem externalManufactureSystem;

    public Selecter(IDatabase database, IExternalManufactureSystem externalManufactureSystem) {
        this.database = database;
        this.externalManufactureSystem = externalManufactureSystem;
    }

    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber) {
        //'Database will give us the vehicle specification'
        var vehicle = database.GetVehicleByRegistrationNumber(registrationNumber);

        //Then we do things with the vehicle object

        //Get the tyre specification
        vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier);

        return vehicle;
    }

    public Tyre GetTyreSpecification(long vehicleIdentifier) {
        //'external manufacture system gets the tyre specification'
        var tyre = externalManufactureSystem.GetTyreSpecification(vehicleIdentifier);

        //Then do thing with the tyre before returning the object

        return tyre;
    }
}

From there it would then be a matter of mocking only the dependencies explicitly needed to test the behavior of the method under test. 从那里开始,只需要模拟显式测试被测方法行为所需的依赖关系。

selecter.GetTyreSpecification has no need to access the database so there is no reason to mock and inject it for the test. selecter.GetTyreSpecification无需访问数据库,因此没有理由为测试模拟和注入它。

[TestMethod]
public void GetTyreSpecification_test() {
    //Arrange
    var vehicleIdentifier = 123456;
    var expected = new Tyre { NumberOfTyres = 4, TyreSize = 18 };

    var mockSystem = new Mock<IExternalManufactureSystem>();
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(expected);

    var selecter = new Selecter(null, mockSystem.Object);

    //Act
    var actual = selecter.GetTyreSpecification(vehicleIdentifier);

    //Assert
    Assert.AreEqual(expected, actual);
}

selecter.GetVehicleByRegistrationNumber however needs to be able to get the tyre specification from the other method so this test would need both dependencies mocked in order for it to be exercised to completion. 然而, selecter.GetVehicleByRegistrationNumber需要能够从其他方法获取轮胎规格,因此该测试需要selecter.GetVehicleByRegistrationNumber两个依赖项,以便将其运用到完成。

[TestMethod]
public void GetVehicleByRegistrationNumber_test() {
    //Arrange
    var vehicleIdentifier = 123456;
    var registrationNumber = "ABC123";
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 };
    var expected = new Vehicle {
        VehicleIdentifier = vehicleIdentifier,
        RegistrationNumber = registrationNumber,
        TyreSpecification = tyre
    };

    var mockSystem = new Mock<IExternalManufactureSystem>();
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre);

    var mockDatabase = new Mock<IDatabase>();
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected);

    var selecter = new Selecter(mockDatabase.Object, mockSystem.Object);

    //Act
    var actual = selecter.GetVehicleByRegistrationNumber(registrationNumber);

    //Assert
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber);
}    

Now with that out of the way, if for example the Selecter class had the GetVehicleByRegistrationNumber as a virtual method, 现在,如果例如Selecter类将GetVehicleByRegistrationNumber作为virtual方法,

public virtual Tyre GetTyreSpecification(long vehicleIdentifier) {
    //...code removed for brevity.
}

There is a way you can use moq to stub the subject under test and mock that method for testing. 有一种方法可以使用moq来存根测试中的主题并模拟该方法进行测试。 This is not always the best design and is considered a code smell. 这并不总是最好的设计,被认为是代码气味。 However there are situations where you will end up in this particular scenario. 但是,在某些情况下,您最终会遇到此特定情况。

[TestMethod]
public void GetVehicleByRegistrationNumber_test2() {
    //Arrange
    var vehicleIdentifier = 123456;
    var registrationNumber = "ABC123";
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 };
    var expected = new Vehicle {
        VehicleIdentifier = vehicleIdentifier,
        RegistrationNumber = registrationNumber,
        TyreSpecification = tyre
    };        

    var mockDatabase = new Mock<IDatabase>();
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected);

    var selecter = new Mock<Selecter>(mockDatabase.Object, null) {
        CallBase = true //So that base methods that are not setup can be called.
    }

    selecter.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre);

    //Act
    var actual = selecter.Object.GetVehicleByRegistrationNumber(registrationNumber);

    //Assert
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber);
} 

In the above example, when selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) is called, the base Selecter wrapped by the mock will be called, which in turn will then call the mocked GetTyreSpecification that was overridden by the setup on the mocked subject under test. 在上面的示例中,当selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) ,将调用由mock包装的基本Selecter ,然后调用模拟的GetTyreSpecification ,该模拟被测试的模拟主题上的设置覆盖。

You tend to see this when testing abstract classes with implemented members that have dependencies on abstract members. 在使用依赖于抽象成员的已实现成员测试抽象类时,您倾向于看到这一点。

In general we use mocks for external dependencies/other object/interface calls being used within our class for which we will write unit tests. 通常我们使用模拟外部依赖项/在我们的类中使用的其他对象/接口调用,我们将编写单元测试。 So when you are writing test for one of your function which internally makes a call to another function within the same class you don't have to mock that function call. 因此,当您为一个函数编写测试时,该函数在内部调用同一个类中的另一个函数时,您不必模拟该函数调用。 However in the internal function if you are making call to an external interface then you will have to mock the external interface instance and write your unit test with expected result 但是在内部函数中,如果要调用外部接口,则必须模拟外部接口实例并编写具有预期结果的单元测试

 var mockSelecter = new Mock<ISelecter>{ CallBase = true };
 mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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