Below both Unit Tests pass and they both test the same thing. Which is the production code uses the right error codes.
The short question is which test is correctly Unit Testing expected behaviour and why?
Please see the code sample below.
System Under Test (SUT)
public interface IRepository {
string GetParameter(int id);
}
public class Repository {
public string GetParameter(int id) {
return "foo";
}
}
public class ErrorInfo{
public string ErrorMessage { get; set; }
}
public interface IErrorProvider {
ErrorInfo BuildErrorMessage(string errorCodes);
}
public class ErrorProvider {
public ErrorInfo BuildErrorMessage(string errorCodes)
{
return new ErrorInfo { ErrorMessage = errorCodes };
}
}
public class DomainClass {
private readonly IRepository _repository;
private readonly IErrorProvider _errorProvider;
public DomainClass(IRepository repository, IErrorProvider errorProvider) {
_repository = repository;
_errorProvider = errorProvider;
}
public List<ErrorInfo> GetErrorList(int id) {
var errorList = new List<ErrorInfo>();
string paramName = _repository.GetParameter(id);
if (string.IsNullOrEmpty(paramName))
{
string errorCodes = string.Format("{0}, {1}", 200, 201);
var error = _errorProvider.BuildErrorMessage(errorCodes);
errorList.Add(error);
}
return errorList;
}
}
UNIT TESTS
//test 1
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes()
{
//Arrange
var stubRepo = new Mock<IRepository>();
stubRepo.Setup(x => x.GetParameter(It.IsAny<int>())).Returns(string.Empty);
const string expectedErrorCodes = "200, 201";
var stubErrorRepo = new Mock<IErrorProvider>();
stubErrorRepo.Setup(e => e.BuildErrorMessage(expectedErrorCodes)).Returns(new ErrorInfo() { ErrorMessage = expectedErrorCodes + " some error message" });
const int parameterId = 5;
var sut = new DomainClass(stubRepo.Object, stubErrorRepo.Object);
//Act
var result = sut.GetErrorList(parameterId);
//Assert
Assert.IsTrue(result.Any(info => info.ErrorMessage.Contains(expectedErrorCodes)));
}
//test 2
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_VerifyTheCorrectErrorCodeBeingUsed()
{
//Arrange
var stubRepo = new Mock<IRepository>();
stubRepo.Setup(x => x.GetParameter(It.IsAny<int>())).Returns(string.Empty);
const string errorCodesPassedIn = "200, 201";
var errorRepoMock = new Mock<IErrorProvider>();
const int parameterId = 5;
var sut = new DomainClass(stubRepo.Object, errorRepoMock.Object);
//Act
sut.GetErrorList(parameterId);
//Assert
errorRepoMock.Verify(x => x.BuildErrorMessage(errorCodesPassedIn));
}
The short question is which test is correctly Unit Testing the expected behaviour and why?
More details: I don't seems to have the confidence on the test1, "GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes" It seems like the stub dictates what the production code should returns.
For example, stubErrorRepo has the setup:
stubErrorRepo.Setup(e => e.BuildErrorMessage(expectedErrorCodes)).Returns(new ErrorInfo() { ErrorMessage = expectedErrorCodes + " some error message" });
This causes production code to return the expected error codes. Then we Assert against the error codes to ensure the result message contains the expected error codes. If I change the value of the "expectedErrorCodes" variable to some other value, the test fails. Note that it is not the production error codes cause the test to fail, it is what have stubbed out "expectedErrorCodes". This make me think that this is not a true Unit test. I believe the stubs should just provide canned answers to the test, and should not dictate the final outcome of the test. Can you call it a mock? I don't know.
But I also think test describes what the production code should return. If you think test is as specification. For example, if the "expectedErrorCodes" codes are not returned, the test fails. Seems like a valid argument too. And of course if I change the production error codes, the test fails as well. But there is something fishy I cannot rule out why this test is not right.
The test2, which is GetErrorList_WhenParameterIsEmpty_VerifyTheCorrectErrorCodeBeingUsed uses the mock.Verify to ensure the correct parameters being used, which seems like a reasonable approach. But I cannot fault the first test either.
Would you prefer test1, test2 or both?
Any thoughts greatly appreciated.