Both unit tests pass and they both test the same thing. The production code uses the right error codes.
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 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)));
}
Unit 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));
}
Which test is correctly unit testing expected behaviour and why?
More details:
I don't seems to have the confidence on Test 1. 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 makes 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.
Test 2 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 Test 1, Test 2 or both?