'Mocking a method that takes a mocked parameter object in C#

The to be tested method

public  string GetPopulatedField(string input)
{
    IParameterCollection sqlParams = CreateParameterCollection();
    sqlParams.Add("param", input);
    string recordCollection = DataAccess.ExecuteSelect(SQL, ref sqlParams);
    return recordCollection ;
}

The test method

public void Test()
{
    // Mocking one of the dependencies used by the ExecuteSelect
    MockHelper.CreateAndRegisterMock<IParameterCollection>();
  
    Mock<Access> mock = new Mock<Access>();
    //mocking the ExecuteSelect Method that takes a string and and IParameterCollection
    mock.Setup(t => t.ExecuteSelect(It.IsAny<string>(),ref It.Ref<IParameterCollection>.IsAny)).Returns("123");

    //The execute Select is called from the GetPopulatedField method
    var x= MockHelper.CreateAndRegisterMock<PopulateField>("Container");
    Assert.AreEqual("123", x.Object.GetPopulatedField("value"));
} 

The issue is when ExecuteSelect is called from GetPopulateField it is using the Mock object and not the actual IParameterCollection, so what is the correct way to Mock the ExecuteSelect properly?

ref It.Ref<IParameterCollection>.IsAny I believe this should be changed to reflect the mocked one.



Solution 1:[1]

In a unit test, it is important to only test a unit, a small portion of code. The smaller the unit,

  • the easier the reason can be found if a failure occurs.
  • the faster the tests can be run.

In order to reduce the code to the relevant unit, two aspects are important:

  • you want to test the real code of the unit as it would be used in the program flow - hence you create a real instance of the class you want to test, not a mock.
  • you do not want to test code that the unit depends on - therefore
    • you structure your code in a way that you can replace dependencies with mocks and
    • you create mocks for the dependencies when testing.

With that in mind, you could change your PopulateField class like this:

public class PopulateField
{
  private readonly IDataAccess _dataAccess; 

  public PopulateField(IDataAccess dataAccess)
  {
    _dataAccess = dataAccess;
  }

  public  string GetPopulatedField(string input)
  {
    IParameterCollection sqlParams = CreateParameterCollection();
    sqlParams.Add("param", input);
    string recordCollection = _dataAccess.ExecuteSelect(SQL, ref sqlParams);
    return recordCollection;
  }

  // ...
}

With an instance of type IDataAccess injected into the constructor and used in the GetPopulatedFields method, you can easily replace it with a mock in the tests:

public void Test()
{
    // Create a mock for the data access
    Mock<IDataAccess> mockDataAccess = new Mock<IDataAccess>();
    //mocking the ExecuteSelect Method that takes a string and and IParameterCollection
    mockDataAccess.Setup(t => t.ExecuteSelect(It.IsAny<string>(),ref It.Ref<IParameterCollection>.IsAny())).Returns("123");

    // Create an instance of PopulateFields so the tests run on real code
    var pf = new PopulateFields(mockDataAccess.Object);
    var actual = pf.GetPopulatedFields("input");
    Assert.AreEqual("123", actual);
} 

Please note that the creation of the IParameterCollection happens in a method of PopulateFields that you cannot replace with this behavior. There are two ways to go about this:

  1. If it is important to also mock the instantiation of the ParameterCollection, you need to create a factory interface that contains a method that returns the IParameterCollection instance. Along with IDataAccess an instance implementing this interface is also injected into the constructor of PopulateFields so you can replace it with a mock when testing.
  2. If you only want to validate that the correct parameters are set when GetPopulatedFields is called, you can add a validation to the setup. Unfortunately, for It.Ref<T> there is only a IsAny and no Is method that allows deciding whether the parameters match, so we solve it with a callback:
mock.Setup(t => t.ExecuteSelect(It.IsAny<string>(),ref It.Ref<IParameterCollection>.IsAny)).Callback((string s, ref IParameterCollection params) => {
  if (!params.Contains("param")) // Adjust conditions to IParameterCollection
    throw new ApplicationException("Parameter was not added");
}.Returns("123");

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Markus