Mocking in Unit Tests - unit-testing

I am trying to test the AddCategory of the following CategoryService.
My problem is that I am having a hard time understanding what to mock/fake.
My attempt at the test is at the bottom.
I am using MOQ, xUnit and FluentAssertions.
I am using FluentValidation for the validators.
Category Service
public class CategoryService : ValidatingServiceBase, ICategoryService
{
private readonly IUnitOfWork unitOfWork;
private readonly IRepository<Category> categoryRepository;
private readonly IRepository<SubCategory> subCategoryRepository;
private readonly IValidationService validationService;
public CategoryService(
IUnitOfWork unitOfWork,
IRepository<Category> categoryRepository,
IRepository<SubCategory> subCategoryRepository,
IValidationService validationService)
: base(validationService)
{
this.unitOfWork = unitOfWork;
this.categoryRepository = categoryRepository;
this.subCategoryRepository = subCategoryRepository;
this.validationService = validationService;
}
public bool AddCategory(Category category)
{
var validationResult = validationService.Validate(category);
if (!validationResult.IsValid)
{
return false;
}
else
{
categoryRepository.Add(category);
return true;
}
}
public bool DoesCategoryExist(string categoryName)
{
return categoryRepository.Query().SingleOrDefault(x => x.Name == categoryName) != null;
}
}
Validation Service
public class ValidationService : ServiceBase, IValidationService
{
private readonly IValidatorFactory validatorFactory;
public ValidationService(IValidatorFactory validatorFactory)
{
Enforce.ArgumentNotNull(validatorFactory, "validatorFactory");
this.validatorFactory = validatorFactory;
}
public ValidationResult Validate<TEntity>(TEntity entity) where TEntity : class
{
var validator = validatorFactory.GetValidator<TEntity>();
return validator.Validate(entity);
}
}
Validator Factory
public class ValidatorFactory : IValidatorFactory
{
public IValidator GetValidator(Type type)
{
Enforce.ArgumentNotNull(type, "type");
return DependencyResolver.Current.GetService(typeof(IValidator<>).MakeGenericType(type)) as IValidator;
}
public IValidator<T> GetValidator<T>()
{
return DependencyResolver.Current.GetService<IValidator<T>>();
}
}
Category Validator
public class CategoryValidator : AbstractValidator<Category>
{
public CategoryValidator(ICategoryService service)
{
RuleFor(x => x.Name)
.NotEmpty()
.Must((category, name) =>
{
return service.DoesCategoryExist(name);
});
}
}
Unit Test Attempt
[Fact]
public void AddCategory_Should_ReturnTrue()
{
var category = new Category() { Name = "Cat1" };
var unitOfWork = new Mock<IUnitOfWork>();
var categoryRepo = new Mock<IRepository<Category>>();
var subCategoryRepo = new Mock<IRepository<SubCategory>>();
var mockCategoryService = new Mock<ICategoryService>();
var categoryValidator = new CategoryValidator(mockCategoryService.Object);
var validatorFactory = new Mock<IValidatorFactory>();
validatorFactory.Setup(x => x.GetValidator<CategoryValidator>()).Returns(categoryValidator as IValidator<CategoryValidator>);
var validationService = new ValidationService(validatorFactory.Object);
var categoryService = new CategoryService(
unitOfWork.Object,
categoryRepo.Object,
subCategoryRepo.Object,
validationService);
categoryService.AddCategory(category);
}

Well for the AddCategory method, I think you really only need two mocks, one for the ValidationService, and one for the CategoryRepository, because the other dependencies aren't exercised in that function and therefore are irrelevant
(the story might be different of course if your ctor throws on null arguments but in this case I think you are OK - albeit you might consider adding these checks in the future :)
Anyway, being pedantic, I'd nearly be inclined to write two (or more - maybe one for null input to verify it throws or returns false or whatever) "unit" tests for this function;
One to verify that given an invalid category, the function returns false,
One to verify that given a valid category, the function calls Add on the CategoryRepository dependency.
So it would look like this (sorry, this is using MSTest syntax as I'm not familiar with xUnit but it's the same idea). Also have not tested below for typos, etc :)
public void AddCategory_InvalidCategory_ShouldReturnFalse()
{
//Arrange
var mockValidator = new Mock<IValidator>();
//no matter what we pass to the validator, it will return false
mockValidator.Setup(v=>v.Validate(It.IsAny<Category>()).Returns(false);
var sut= new CategoryService(null,null,null,mockValidator.Object);
bool expected = false;
//ACT
bool actual = sut.AddCategory(new Category());
//ASSERT
Assert.AreEqual(expected,actual,"Validator didn't return false as expected");
}
public void AddCategory_ValidCategory_ShouldCallRepositoryAdd()
{
//Arrange
var mockValidator = new Mock<IValidator>();
//no matter what we pass to the validator, it will return true
mockValidator.Setup(v=>v.Validate(It.IsAny<Category>()).Returns(true);
var mockRepo = new Mock<IRepository<SubCategory>>();
mockRepo.Setup(r=>r.Add(It.IsAny<Category>())); //do not know or care what happens as this is a void method.
var sut= new CategoryService(null,mockRepo.Object,null,mockValidator.Object);
bool expected = false;
//ACT
bool actual = sut.AddCategory(new Category());
//ASSERT
mockRepo.Verify(r=>r.Add(It.IsAny<Category>(),Times.Exactly(1),"Repo ADD method not called or called too many times, etc");
Assert.AreEqual(expected,actual,"Add was called BUT the AddCategoryMethod didn't return true as expected"); //and of course you could be totally pedantic and create a new test method for that last assert ;)
}
The reason I favour this approach is because it forces you to consider the behaviour of the method under test, as well as ensuring that you don't involve any dependencies that are not being tested plus it means your test methods only create exactly what they need to in order to run the tests (and of course you can create some setup/teardown helpers to pre-create those mocks for you);
Of course you could put all the above into a single method but for the sake of saving a few LOC I hope you'll agree that having two separate tests to verify two separate behaviours is a more robust approach.
Just my 2c. hope it helps!

Related

Unit testing when Database is involved

I'm quite new to unit testing and I need an hand to understand if I'm doing things in the correct way. My major problem is regarding the DB testing... Here's my code then I'll expose my perplexities
Consider this class that's an item of a pipeline I've to perform
public class RetrieveApplicationUsernamePipelineStep : IPipelineStep
{
public const string RetrieveApplicationUsernameKey = "RetrieveApplicationUsername";
private readonly IRetrieveApplicationUserRepository repository;
public int Order => 3;
public string Name => RetrieveApplicationUsernameKey;
public RetrieveApplicationUsernamePipelineStep(IRetrieveApplicationUserRepository repository)
{
this.repository = repository;
}
public async Task<IDictionary<string, object>> Action(IDictionary<string, object> context)
{
string res = await repository.GetApplicationUser(context);
context[Resources.ApplicationUser] = res;
return context;
}
}
I wrote the following tests
[TestFixture]
public class RetrieveApplicationUsernamePipelineStepTests
{
private IRetrieveApplicationUserRepository retrieveApplicationUserRepository;
[OneTimeSetUp]
public void Start()
{
var configuration = new ConfigurationFromConfigFile();
retrieveApplicationUserRepository = new RetrieveApplicationUserRepository(configuration);
}
[Test]
public async Task ActionSuccessfullyCompleted()
{
var context = new Dictionary<string, object>();
var repository = Substitute.For<IRetrieveApplicationUserRepository>();
repository.GetApplicationUser(context).Returns("user1");
var pipeline = new RetrieveApplicationUsernamePipelineStep(repository);
var res = await pipeline.Action(context);
Assert.IsNotNull(res[Resources.ApplicationUser]);
Assert.IsNotEmpty((string)res[Resources.ApplicationUser]);
}
[Test]
public void ActionFailingCompleted()
{
var context = new Dictionary<string, object>();
var repository = Substitute.For<IRetrieveApplicationUserRepository>();
repository.GetApplicationUser(context).Throws(new UserMappingNotFoundException());
var pipeline = new RetrieveApplicationUsernamePipelineStep(repository);
Assert.ThrowsAsync<UserMappingNotFoundException>(async () => await pipeline.Action(context));
}
[Test]
public void NameTest()
{
var pipeline = new RetrieveApplicationUsernamePipelineStep(retrieveApplicationUserRepository);
Assert.IsTrue(pipeline.Name == RetrieveApplicationUsernamePipelineStep.RetrieveApplicationUsernameKey);
}
[Test]
public void OrderTest()
{
var pipeline = new RetrieveApplicationUsernamePipelineStep(retrieveApplicationUserRepository);
Assert.IsTrue(pipeline.Order == 3);
}
}
And those test works fine since for ActionSuccessfullyCompleted and ActionFailingCompleted I substitute the IRetrieveApplicationUserRepository's result with my expected one.
The real implementation of ther repository is
public class RetrieveApplicationUserRepository : IRetrieveApplicationUserRepository
{
#region Variables
private readonly IConfiguration configuration;
#endregion
#region Ctor
public RetrieveApplicationUserRepository(IConfiguration configuration)
{
this.configuration = configuration;
}
#endregion
#region IRetrieveApplicationUserRepository
public async Task<string> GetApplicationUser(IDictionary<string, object> context)
{
if (configuration.AppSettings[Resources.ApplicationUserFromDomainUserKey] == null)
throw new KeyNotFoundException(Resources.ApplicationUserFromDomainUserKey);
if (string.IsNullOrEmpty(configuration.ConnectionString))
throw new NullReferenceException();
string storedProcedure = configuration.AppSettings.Get(Resources.ApplicationUserFromDomainUserKey);
string result;
using (var sqlConnection = new SqlConnection(configuration.ConnectionString))
{
using (var sqlCommand = new SqlCommand(storedProcedure, sqlConnection))
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.AddWithValue("#DOMAINUSER", context[Resources.DomainUser]);
sqlCommand.Parameters.AddWithValue("#DOMAIN", context[Resources.DomainName]);
sqlCommand.Parameters.AddWithValue("#APPID", context[Resources.ApplicationId]);
sqlConnection.Open();
result = (string)await sqlCommand.ExecuteScalarAsync();
}
}
if (result == null)
throw new UserMappingNotFoundException();
return result;
}
#endregion
}
Here're the questions :
Are the test I wrote correct?
I've seen using Resharper's Code Coverage that it wants me to test-cover the properties...is there a way I can avoid this? is this test meaningful?
What's your approach when you've to unit test component that're related to DB? Have you got a real-db that's used for test? Consider that the real DB is about 10Gb so I don't want to have a copy as mdf (condider I can have this) just to test a small portion of the DB
Talking with my colleagues they told me to use test just for TDD while I wish to use them to avoid regressions
Going back to the DB question, I don't want to have a test where I write if username is "John" and maybe tomorrow John user won't be present in the DB anymore,so that the test I expect to pass wont' pass anoymore
The usual approach is to isolate the database side with an abstraction, so you can provide a test dummy (mock, fake, etc) of that abstraction. Only test an actual database when you do the integration testing.
For the tests of database stored procedures, you may well want a different test harness, creating a new test database in memory (equivalently, in a RAM-backed filesystem). You only need to populate enough data for the individual test (we're doing functional testing here, not performance testing), and you may be able to retain table structure across tests with judicious use of rollback.
I have done this, but it's some time ago, so I'll refrain from giving examples that may be no longer state-of-the-art (even if the code does still exist, and if I could find it).

How to write unit test for ActionFilter when using Service Locator

I am planning to write an ActionFilter for business validation and in which some services will be resolved via Service Locator(I know this is not good practice and as far as possible i avoid Service Locator pattern, but for this case i want to use it).
OnActionExecuting method of the filter is something like this:
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
// get validator for input;
var validator = actionContext.HttpContext.RequestServices.GetService<IValidator<TypeOfInput>>();// i will ask another question for this line
if(!validator.IsValid(input))
{
//send errors
}
}
Is it possible to write unit test for above ActionFilterand how?
Here is an sample on how to create a mock (using XUnit and Moq framework) to verify that the IsValid method is called and where the mock returns an false.
using Dealz.Common.Web.Tests.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using System;
using Xunit;
namespace Dealz.Common.Web.Tests.ActionFilters
{
public class TestActionFilter
{
[Fact]
public void ActionFilterTest()
{
/****************
* Setup
****************/
// Create the userValidatorMock
var userValidatorMock = new Mock<IValidator<User>>();
userValidatorMock.Setup(validator => validator
// For any parameter passed to IsValid
.IsValid(It.IsAny<User>())
)
// return false when IsValid is called
.Returns(false)
// Make sure that `IsValid` is being called at least once or throw error
.Verifiable();
// If provider.GetService(typeof(IValidator<User>)) gets called,
// IValidator<User> mock will be returned
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock.Setup(provider => provider.GetService(typeof(IValidator<User>)))
.Returns(userValidatorMock.Object);
// Mock the HttpContext to return a mockable
var httpContextMock = new Mock<HttpContext>();
httpContextMock.SetupGet(context => context.RequestServices)
.Returns(serviceProviderMock.Object);
var actionExecutingContext = HttpContextUtils.MockedActionExecutingContext(httpContextMock.Object, null);
/****************
* Act
****************/
var userValidator = new ValidationActionFilter<User>();
userValidator.OnActionExecuting(actionExecutingContext);
/****************
* Verify
****************/
// Make sure that IsValid is being called at least once, otherwise this throws an exception. This is a behavior test
userValidatorMock.Verify();
// TODO: Also Mock HttpContext.Response and return in it's Body proeprty a memory stream where
// your ActionFilter writes to and validate the input is what you desire.
}
}
class User
{
public string Username { get; set; }
}
class ValidationActionFilter<T> : IActionFilter where T : class, new()
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext actionContext)
{
var type = typeof(IValidator<>).MakeGenericType(typeof(T));
var validator = (IValidator<T>)actionContext.HttpContext
.RequestServices.GetService<IValidator<T>>();
// Get your input somehow
T input = new T();
if (!validator.IsValid(input))
{
//send errors
actionContext.HttpContext.Response.WriteAsync("Error");
}
}
}
internal interface IValidator<T>
{
bool IsValid(T input);
}
}
HttpContextUtils.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Generic;
namespace Dealz.Common.Web.Tests.Utils
{
public class HttpContextUtils
{
public static ActionExecutingContext MockedActionExecutingContext(
HttpContext context,
IList<IFilterMetadata> filters,
IDictionary<string, object> actionArguments,
object controller
)
{
var actionContext = new ActionContext() { HttpContext = context };
return new ActionExecutingContext(actionContext, filters, actionArguments, controller);
}
public static ActionExecutingContext MockedActionExecutingContext(
HttpContext context,
object controller
)
{
return MockedActionExecutingContext(context, new List<IFilterMetadata>(), new Dictionary<string, object>(), controller);
}
}
}
As you can see, it's quite a mess, you need to create plenty of mocks to simulate different responses of the actuall classes, only to be able to test the ActionAttribute in isolation.
I like #Tseng's above answer but thought of giving one more answer as his answer covers more scenarios (like generics) and could be overwhelming for some users.
Here I have an action filter attribute which just checks the ModelState and short circuits(returns the response without the action being invoked) the request by setting the Result property on the context. Within the filter, I try to use the ServiceLocator pattern to get a logger to log some data(some might not like this but this is an example)
Filter
public class ValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ValidationFilterAttribute>>();
logger.LogWarning("some message here");
context.Result = new JsonResult(new InvalidData() { Message = "some messgae here" })
{
StatusCode = 400
};
}
}
}
public class InvalidData
{
public string Message { get; set; }
}
Unit Test
[Fact]
public void ValidationFilterAttributeTest_ModelStateErrors_ResultInBadRequestResult()
{
// Arrange
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock
.Setup(serviceProvider => serviceProvider.GetService(typeof(ILogger<ValidationFilterAttribute>)))
.Returns(Mock.Of<ILogger<ValidationFilterAttribute>>());
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = serviceProviderMock.Object;
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var actionExecutingContext = new ActionExecutingContext(
actionContext,
filters: new List<IFilterMetadata>(), // for majority of scenarios you need not worry about populating this parameter
actionArguments: new Dictionary<string, object>(), // if the filter uses this data, add some data to this dictionary
controller: null); // since the filter being tested here does not use the data from this parameter, just provide null
var validationFilter = new ValidationFilterAttribute();
// Act
// Add an erorr into model state on purpose to make it invalid
actionContext.ModelState.AddModelError("Age", "Age cannot be below 18 years.");
validationFilter.OnActionExecuting(actionExecutingContext);
// Assert
var jsonResult = Assert.IsType<JsonResult>(actionExecutingContext.Result);
Assert.Equal(400, jsonResult.StatusCode);
var invalidData = Assert.IsType<InvalidData>(jsonResult.Value);
Assert.Equal("some messgae here", invalidData.Message);
}

Moq Error : Moq.MockVerificationException: The following setups were not matched

I wanna test my method with mock but it throw this exception. My class is this (this class do some simple actions on a file as though unzipping the file) :
public class FileActions
{
public virtual void Decompress(FileInfo fileInfo, DirectoryInfo directoryInfo)
{
ZipFile.ExtractToDirectory(fileInfo.FullName, directoryInfo.FullName);
}
public virtual FileInfo GetConvertedFileToZip(FileInfo fileInfo)
{
try
{
var changeExtension = Path.ChangeExtension(fileInfo.FullName, "zip");
File.Move(fileInfo.FullName, changeExtension);
return new FileInfo(changeExtension);
}
catch (Exception)
{
throw new FileNotFoundException();
}
}
}
and this is my test :
public void TestMockedMethodForNotNull()
{
var mock = new Mock<FileActions>();
var fInfo = new FileInfo(#"D:\ZipFiles\elmah.nupkg");
mock.Setup(s => s.GetConvertedFileToZip(fInfo)).Verifiable();
mock.VerifyAll();
}
So, why does it get this Error :
Moq.MockVerificationException: The following setups were not matched:
FileActions2 s => s.GetConvertedFileToZip(D:\ZipFiles\elmah.nupkg)
There are several issues with your Unit Test. I will only highlight the mocking side of things, as it relevant to the question you ask. Also your question has refer to "FileActions2", and I think this
a mistake when you originally add the question.
You Test:
[TestMethod]
public void TestMockedMethodForNotNull()
{
var mock = new Mock<FileActions>();
var fileInfo = new FileInfo(#"D:\ZipFiles\elmah.nupkg");
mock.Setup(s => s.GetConvertedFileToZip(fileInfo)).Verifiable();
mock.VerifyAll();
}
The way you have written this test, Moq won't verify on GetConvertedFileToZip
This test fail fundamentally because Moq cannot provide an override for a virtual method GetConvertedFileToZip. You must create a proxy i,e mock.Object.
If you modify your test in such a way so your SUT (Sysytem Under Test), consumes an instance of the mocked object/proxied object
your verify would work partially (partially means you are heading right direction). Still something else to fix which I have described below.
Assuming your SUT is like below
public class Sut
{
public void Do(FileActions fileActions)
{
var fileInfo = new FileInfo(#"D:\ZipFiles\elmah.nupkg");
var s = fileActions.GetConvertedFileToZip(fileInfo);
}
}
Your Test
[TestMethod]
public void TestMockedMethodForNotNull()
{
var mock = new Mock<FileActions>();
var fileInfo = new FileInfo(#"D:\ZipFiles\elmah.nupkg");
mock.Setup(s => s.GetConvertedFileToZip(fileInfo)).Verifiable();
var sut = new Sut();
sut.Do(mock.Object);
mock.VerifyAll();
}
This would produce an exception. This is because fileInfo you have setup on does not match the verification, when invoke via the Sut.
If you were to modify this test as below, this would succeed
[TestMethod]
public void TestMockedMethodForNotNull()
{
var mock = new Mock<FileActions>();
//var fileInfo = new FileInfo(#"D:\ZipFiles\elmah.nupkg");
mock.Setup(s => s.GetConvertedFileToZip(It.IsAny<FileInfo>())).Verifiable();
var sut = new Sut();
sut.Do(mock.Object);
mock.VerifyAll();
}

how do you mock an xml for unit testing?

I need to unit testing this GetData method.
public MessageResponse GetData(XmlElement requestElement)
{
MessageResponse MsgResponse = new MessageResponse();
if (requestElement.Attributes["employeeNo"] == null){
MsgResponse.Messages = new List<string>();
MsgResponse.Messages.Add("Attribute employeeNo is missing");
MsgResponse.Error = true;
return MsgResponse;
}
if (requestElement.Attributes["xmlEmployeeName"] == null){
MsgResponse.Messages.Add("Attribute xmlEmployeeName is missing");
MsgResponse.Error = true;
return MsgResponse;
}
return MsgResponse;
}
this method needs a XmlElement parameter. how do I mock it? in my code, I first created a xmlDocument, then load the xml file.
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);
requestElement = doc.DocumentElement;
for me to test it, first i need to create a xml file without employeeNo, the create antoher one without name, maybe alot more for other scenarios. it just seems like alot work. is there a better way to test it?
should I use moq or other testing framework to simplify the testing?
You can just create the element you want to test with, w/o reading a file at all:
var doc = new XmlDocument();
doc.LoadXml("<MyTestElement/>");
var myTestElement = doc.DocumentElement;
myTestElement.Attributes["employeeNo"] = "fakeId";
var response = myTestResponder.GetData(myTestElement);
//assert whatever you need to
NOTE: every time you find out that the test is too hard to write, usually this means that your class/method does too much.
I would assume, that your method verifies the input, than does something with the data provided. I would suggest that you abstract the data reading part (using some xml deserializer) to populate the data model you need for your application.
Then run validation on the result of the deserialized data. Something like:
public MessageResponse GetData(XmlElement requestElement)
{
var data = _xmlDeserializer.Deserialize(requestElement);
var validationResult = _validator.Validate(data);
if (validationResult.Errors.Count > 0)
{
//populate errors
return result;
}
_dataProcessor.DoSomethingWithData(data);
}
Take a look at FluentValidation for a nice validation library.
If you go the above route, then your tests will be much simpler.
[TestMethod]
public void GetData_Returns_Correct_Message_When_EmployeeNo_Is_Null()
{
var inputWithoutEmployeeNo = GetElement(#"<input></input>");
var actual = GetData(inputWithoutEmployeeNo);
Assert.IsTrue(actual.Error, "Error should be true when employee no. is missing");
Assert.IsNotNull(actual.Messages);
Assert.AreEqual(1, actual.Messages.Count);
Assert.AreEqual("Attribute employeeNo is missing", actual.Messages[0]);
}
private XmlElement GetElement(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
While working on the unit test, I found out that the code throws a NullReferenceException.
The following unit test demonstrates the issue:
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public void GetData_Throws_NullReferenceException_When_EmployeeNo_Is_Not_Null_And_XmlEmployeeName_Is_Null()
{
var inputWithoutEmployeeNo = GetElement(#"<input employeeNo='123'></input>");
GetData(inputWithoutEmployeeNo);
}
Using Moq
using System;
using System.Xml;
using Moq;
using NUnit.Framework;
namespace MockXmlTest
{
[TestFixture]
public class MyServiceTests
{
private MockSetup _mockSetup;
[SetUp]
public void Init()
{
_mockSetup = MockSetup.HappySetup();
}
[Test]
public void MyService_Should_Return_Guid()
{
//Arrange
var myService = _mockSetup.MyService.Object;
var id = 42;
var expected = Guid.Empty.ToString();
//Act
var actual = myService.GetXml(id);
//Assert
Assert.AreEqual(expected, actual.FirstChild.InnerText);
}
}
public class MyService : IMyService
{
public XmlDocument GetXml(int id)
{
var doc = new XmlDocument();
//Do real stuff
return doc;
}
}
public interface IMyService
{
XmlDocument GetXml(int id);
}
public class MockSetup
{
public Mock<IMyService> MyService { get; set; }
public MockSetup()
{
MyService = new Mock<IMyService>();
}
public static MockSetup HappySetup()
{
var mockSetup = new MockSetup();
var mockDoc = CreateMockDoc();
//Matches any id of an integer, returns a XmlDocument mock
mockSetup.MyService.Setup(m => m.GetXml(It.IsAny<int>())).Returns(mockDoc);
return mockSetup;
}
private static XmlDocument CreateMockDoc()
{
//<Main><MyGuid>00000000-0000-0000-0000-000000000000</MyGuid></Main>
XmlDocument mockDoc = new XmlDocument();
XmlElement el = (XmlElement)mockDoc.AppendChild(mockDoc.CreateElement("Main"));
el.AppendChild(mockDoc.CreateElement("MyGuid")).InnerText = It.IsAny<Guid>().ToString();
return mockDoc;
}
}
}

Unit Test Assert against end result or verifying whether the parameters were called using Moq

Below is a class (Class1) that I want to test, but I'm not fully satisfied with my Unit Test. Please see below code samples.
System Under Test
public interface IRepository {
string GetParameter(int id);
}
public class Repository {
public string GetParameter(int id) {
return "foo";
}
}
public class ErrorInfo {
public string ErrorCodes { get; set; }
}
public interface IErrorProvider {
ErrorInfo BuildErrorMessage(string errorCodes);
}
public class ErrorProvider {
public ErrorInfo BuildErrorMessage(string errorCodes) {
return new ErrorInfo(){ErrorCodes = errorCodes};
}
}
public class Class1 {
private readonly IRepository _repository;
private readonly IErrorProvider _errorProvider;
public Class1(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
Below test passes and we check whether the correct error codes being used within the system under test.
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes2() {
//Arrange
var stubRepo = new Mock<IRepository>();
stubRepo.Setup(x => x.GetParameter(It.IsAny<int>())).Returns(string.Empty);
var stubErrorMock = new Mock<IErrorProvider>();
const int id = 5;
var sut = new Class1(stubRepo.Object, stubErrorMock.Object);
//Act
var result = sut.GetErrorList(id);
//Verify
string verifiableErrorCodes = "200, 201";
stubErrorMock.Verify(x => x.BuildErrorMessage(verifiableErrorCodes));
}
However I would prefer testing the end result. For example, I want to Assert against the error codes that have been used in the production code. Below test fails but I like to know your thoughts on how to Assert against the errorCodes that has been used in the system under test.
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes1() {
//Arrange
var stubRepo = new Mock<IRepository>();
stubRepo.Setup(x => x.GetParameter(It.IsAny<int>())).Returns(string.Empty);
string expectedErrorCodes = "200, 201";
var stubErrorRepo = new Mock<IErrorProvider>();
stubErrorRepo.Setup(e => e.BuildErrorMessage(It.IsAny<string>()));
const int id = 5;
var sut = new Class1(stubRepo.Object, stubErrorRepo.Object);
//Act
var result = sut.GetErrorList(id);
//Assert
Assert.AreEqual(expectedErrorCodes, result.Single().ErrorCodes);
}
What would be the correct way to test this error codes that has been used in the system?
I suggest to mock only the IRepository and use a real IErrorProvider. Then you can call GetErrorList(id) and check the result.
There is not really right or wrong answer and we have decided to use the Assert test as it test the end result.
I took the TDD approach and re-implemented/analysed as below.
Start with a failing test (to simplify the code I removed the Repository from both test and the sut)
//AssertTest
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes1()
{
//Arrange
const string expectedErrorCodes = "200, 201";
var stubErrorRepo = new Mock<IErrorProvider>();
stubErrorRepo.Setup(e => e.BuildErrorMessage(expectedErrorCodes)).Returns(new ErrorInfo() { ErrorCodes = expectedErrorCodes });
var sut = new Class1(stubErrorRepo.Object);
//Act
var result = sut.GetErrorList();
//Assert
Assert.AreEqual(expectedErrorCodes, result.Single().ErrorCodes);
}
//SUT
public IEnumerable<ErrorInfo> GetErrorList(int id)
{
yield return new ErrorInfo();
}
As you would expect the test fail.
Now if write enough production code to make this test pass.
public IEnumerable<ErrorInfo> GetErrorList()
{
yield return _errorProvider.BuildErrorMessage("200, 201");
}
The VerifyTest would still fail for the above SUT.
//VerifyTest
[TestMethod]
public void GetErrorList_WhenParameterIsEmpty_ReturnsExpectedErrorCodes2()
{
//Arrange
var stubErrorMock = new Mock<IErrorProvider>();
var sut = new Class1(stubErrorMock.Object);
//Act
sut.GetErrorList();
//Verify
string verifiableErrorCodes = "200, 201";
stubErrorMock.Verify(x => x.BuildErrorMessage(verifiableErrorCodes));
}
However if I want this test to pass, I can write the below production code as below
public IEnumerable<ErrorInfo> GetErrorList()
{
_errorProvider.BuildErrorMessage("200, 201");
return null;
}
Now the VerifyTest passes, but the AssertTest fails.
Both tests are valid in their own ways. However they test different semantics.
AssertTest test whether the end result contains the correct error codes. Verify test ensures
the method is called with the correct error codes. It is important to note that
the end value of the Assert test is based on the setup method "match" provided by the Moq
framework. In other words the setup dictates what the end result would be.
AssertTest would fail if the setup is configured incorrectly or the production code uses error codes that does not match the setup configuration.
It is preferred to use the AssertTest as it test the end result.