'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:
- 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
IParameterCollectioninstance. Along withIDataAccessan instance implementing this interface is also injected into the constructor ofPopulateFieldsso you can replace it with a mock when testing. - If you only want to validate that the correct parameters are set when
GetPopulatedFieldsis called, you can add a validation to the setup. Unfortunately, forIt.Ref<T>there is only aIsAnyand noIsmethod 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 |
