Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. 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.

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?

like image 552
user2227138 Avatar asked May 14 '13 08:05

user2227138


People also ask

How do you mock a method call inside another method in the same class?

We can mock runInGround(String location) method inside the PersonTest class as shown below. Instead of using mock(class) here we need to use Mockito. spy() to mock the same class we are testing. Then we can mock the method we want as follows.

How do you mock a class method in C#?

Trying to mock a method that is called within another method. // code part public virtual bool hello(string name, int age) { string lastName = GetLastName(); } public virtual string GetLastName() { return "xxx"; } // unit test part Mock<Program> p = new Mock<Program>(); p. Setup(x => x. GetLastName()).

Is Moq a unit test?

The Moq framework is an open source unit testing framework that works very well with . NET code and Phil shows us how to use it.

What can be mocked with Moq?

Unit testing is a powerful way to ensure that your code works as intended. It's a great way to combat the common “works on my machine” problem. Using Moq, you can mock out dependencies and make sure that you are testing the code in isolation.


2 Answers

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.

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.

[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.

[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,

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. 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.

You tend to see this when testing abstract classes with implemented members that have dependencies on abstract members.

like image 190
Nkosi Avatar answered Sep 22 '22 07:09

Nkosi


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. 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.

like image 35
levelnis Avatar answered Sep 19 '22 07:09

levelnis