I Don't Understand The Difference In These Unit Tests - unit-testing

I'm using Moq, xUnit and Prism 4. My unit test's objective is to fire an event and confirm that a property has changed in my view model to match the value from the event. This test, by the way, fails (Expected:5, Actual:0):
// Version One
[Fact]
public void Should_set_DayCount_on_DayCountChangedEvent()
{
var eaMock = new Mock<IEventAggregator>();
eaMock.SetupCurriculumEvents(); // see below
var vm = new CurriculumItemViewModel(eaMock.Object, _systemStatus.Object);
vm.Load(_newItem);
var dayCount = 5;
eaMock.Object.GetEvent<DayCountChangedNotification>().Publish(dayCount);
Assert.Equal(dayCount, _vm.DayCount);
}
I got tired of setting up my event aggregator mock everywhere, so I created an extension method to do the dirty work for me:
public static void SetupCurriculumEvents(this Mock<IEventAggregator> eaMock)
{
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(new DayCountChangedNotification());
// lots of other "notification" events here as well
}
Then I realized that I'm creating a new event every time it's retrieved from the mock event aggregator, so the Subscribe() on one instance (in the VM) isn't on the same instance as the Publish(dayCount) in my test.
Well, methinks, let's just always use the same object (by overwriting the extension method's Setup() for this event) and we're gonna be good:
// Version Two
[Fact]
public void Should_set_DayCount_on_DayCountChangedEvent()
{
var dayCountChangedEvent = new DayCountChangedNotification();
var eaMock = new Mock<IEventAggregator>();
eaMock.SetupCurriculumEvents(); // still need this for all the other events
// overwrite the setup from the extension method
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(dayCountChangedEvent);
var vm = new CurriculumItemViewModel(eaMock.Object, _systemStatus.Object);
vm.Load(_newItem);
var dayCount = 5;
dayCountChangedEvent.Publish(dayCount);
Assert.Equal(dayCount, _vm.DayCount);
}
... which also fails spectacularly.
For some reason, I decided to try refactoring the extension method, (and reverted the unit test back to Version One):
public static class MockingExtensions
{
private static DayCountChangedNotification DayNotification = new DayCountChangedNotification();
public static void SetupCurriculumEvents(this Mock<IEventAggregator> eaMock)
{
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(DayNotification);
// etc...
}
}
... which to my mind, is basically the same thing - I'm always returning the same instance of the Event.
Here's the kicker: this test passes.
That's great and everything, but I don't understand why it passes - and if I don't understand why it's passing, then I don't really know if it's right or not.
Accepted answer needs to explain two things:
Why does the refactored extension method with the static instance pass?
Why doesn't Version Two pass?

I tried to reproduce your problem and created the code below. All three tests succeeded so i think something is missing in the question. It is important to know that moqObject.Setup(...).Return(true) is not the same as moqObject.Setup(...).Return(() => true). See for more info here
namespace Test
{
using Microsoft.Practices.Prism.Events;
using Moq;
using System;
using Xunit;
// Moq 4.2.1510.2205
// Prism 4.0.0.0
// xunit 2.1.0
public class CurriculumItemViewModel
{
public CurriculumItemViewModel(IEventAggregator agg)
{
agg.GetEvent<DayCountChangedNotification>().Subscribe((int? x) => {
DayCount = x.Value;
});
}
public int DayCount { get; set; }
}
public class DayCountChangedNotification : CompositePresentationEvent<int?>
{
public DateTime Created = DateTime.Now;
}
public static class Extensions
{
private static DayCountChangedNotification DayNotification = new DayCountChangedNotification();
public static void SetupCurriculumEventsV1(this Mock<IEventAggregator> eaMock)
{
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(new DayCountChangedNotification());
}
public static void SetupCurriculumEventsV2(this Mock<IEventAggregator> eaMock)
{
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(DayNotification);
}
}
public class TestClass
{
[Fact]
public void ShouldSetDayCountOnDayCountChangedEvent1a()
{
// Arrange
var dayCount = 5;
var eaMock = new Mock<IEventAggregator>();
eaMock.SetupCurriculumEventsV1();
var vm = new CurriculumItemViewModel(eaMock.Object);
var notification = eaMock.Object.GetEvent<DayCountChangedNotification>();
var notification2 = eaMock.Object.GetEvent<DayCountChangedNotification>();
// Act
notification.Publish(dayCount);
// Assert
Assert.Equal(dayCount, vm.DayCount);
}
[Fact]
public void ShouldSetDayCountOnDayCountChangedEvent2()
{
// Arrange
var dayCount = 5;
var eaMock = new Mock<IEventAggregator>();
eaMock.SetupCurriculumEventsV1();
// This will override the setup done by SetupCurriculumEventsV1
var notification = new DayCountChangedNotification();
eaMock.Setup(ea => ea.GetEvent<DayCountChangedNotification>())
.Returns(notification);
var vm = new CurriculumItemViewModel(eaMock.Object);
// Act
notification.Publish(dayCount);
// Assert
Assert.Equal(dayCount, vm.DayCount);
}
[Fact]
public void ShouldSetDayCountOnDayCountChangedEvent1b()
{
// Arrange
var dayCount = 5;
var eaMock = new Mock<IEventAggregator>();
eaMock.SetupCurriculumEventsV2();
var vm = new CurriculumItemViewModel(eaMock.Object);
var notification = eaMock.Object.GetEvent<DayCountChangedNotification>();
// Act
notification.Publish(dayCount);
// Assert
Assert.Equal(dayCount, vm.DayCount);
}
}
}

Related

Unit testing view model that uses SelectMany to call an async method in ReactiveUI

I am new to ReactiveUI and trying to test a view model that looks like this:
public interface IService
{
Task<SessionModel> GetData(string id);
}
/// Provides a group of schedulers available to be used
public interface ISchedulers
{
IScheduler Default { get; }
IScheduler Dispatcher { get; }
}
public class MyVm : ReactiveObject
{
IService service;
public MyVm(ISchedulers schedulers, IService service)
{
this.service = service;
this.session = this.WhenAnyValue(x => x.SessionId)
.SelectMany(SearchSession)
.ObserveOn(schedulers.Default)
.ToProperty(this, x => x.Session);
}
private async Task<SessionModel> SearchSession(string id)
{
return await this.service.GetData(id);
}
private string sessionId;
public string SessionId
{
get => sessionId;
set => this.RaiseAndSetIfChanged(ref sessionId, value);
}
readonly ObservableAsPropertyHelper<SessionModel> session;
public SessionModel Session
{
get { return session.Value; }
}
}
public class SessionModel { }
I'm mocking the service call to return dummy data, but not sure what I need to do with a TestScheduler in order to get the SelectMany to work.
Here's a test class that shows how i would create a test for the view model. The goal is to eventually be able to check that the model got set:
[TestClass]
public class MyVmTests
{
[TestMethod]
public void CreateClass
{
var subject = new MyVm(/*pass in mocks*/);
subject.SessionId="test";
Assert.IsNotNull(subject.Session);
}
}
I don't think using TestScheduler is necessary. The following passes for me (using Moq):
var mockSchedulers = new Mock<ISchedulers>();
mockSchedulers.Setup(s => s.Default).Returns(Scheduler.Immediate);
var id = "123";
var mockService = new Mock<IService>();
var returnSession = new SessionModel();
mockService.Setup(s => s.GetData(It.Is<string>(i => i == id)))
.ReturnsAsync(returnSession);
var target = new MyVm(mockSchedulers.Object, mockService.Object);
target.SessionId = id;
Assert.IsNotNull(target.Session);
Assert.AreEqual(returnSession, target.Session);
TestScheduler is best when you're trying to test something with time (like a Delay, proving that the Delay actually happened). You're not really doing that here.

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 to reset autofac container?

In my tests I setup an autofac container, it returns some real implementation and some mocks (DB, external systems).
The problem is that after each test I Dispose the container and create a new one:
Autofac.IContainer.Dispose() and Container = builder.Build();
The already registered instances are still there.
How can I reset the container so it would be 'like new' again?
The reason why I want to do is - I want to replace one mocked instance with another. It's being registered as Singleton.
---- EDIT
Thanks for the answers. I decided to add some more code and describe what actually I'm trying to achieve. But that is actually a topic for another (prabably already answered question - unit testing CQRS).
My app contains static IContainer property:
public static IContainer Container { get; private set; }
After each test execution I create it again by calling those two methods:
public static ContainerBuilder Compose(IEnumerable<DependencyProvider> dependencyProviders)
{
var collection = dependencyProviders as List<DependencyProvider> ?? dependencyProviders.ToList();
var included = new HashSet<DependencyProvider>(collection);
var includedTypes = new HashSet<Type>(collection.Select(x => x.GetType()));
var currentWorkingSet = new List<DependencyProvider>(collection);
while (true)
{
var candidates = currentWorkingSet.SelectMany(x => x.GetDependencies());
var typesToBeAdded = candidates.Where(x => !includedTypes.Contains(x)).Distinct().ToList();
if (typesToBeAdded.Any() == false)
break;
currentWorkingSet.Clear();
foreach (var type in typesToBeAdded)
{
includedTypes.Add(type);
var instance = CreateInstance(type);
included.Add(instance);
currentWorkingSet.Add(instance);
}
}
return BuildContainer(included);
}
and
TestDependencyProvider dependencyProvider = new TestDependencyProvider()
var builder = Compose(new[] { dependencyProvider });
Container = builder.Build();
The TestDependencyProvider is created for each test and contains moqed instances. It registers those mocks and x.GetDependencies() uses the original container registrations i.e. container.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsClosedTypesOf(typeof(IAggregateBusinessRuleForEvent<,>));
The type I'm mostly interested in is one of implementations of IAggregateBusinessRuleForEvent<,>).
public class RuleA: IAggregateBusinessRuleForEvent<AEvent, Something>
{
private readonly IDependency _dependency;
public RejectCompanyNameRule(IDependency dependency)
{
_dependency = dependency;
}
}
So even though I create this container again that RuleA is still there and all of my test are using same instance with same _dependency :/
It's still not entierly clear why code looks how it looks, I'm trying to understand it by adding tests...
------- EDIT 2
Following Jimmy's advice I've implemented a sample using Update me
public interface IExample<T>
{
void Hello();
}
public interface IExampleDependecy
{
void SaySomething();
}
public class Example : IExample<string>
{
private IExampleDependecy _dependecy;
public Example(IExampleDependecy dependecy)
{
_dependecy = dependecy;
}
public void Hello()
{
Console.WriteLine("Hello");
_dependecy.SaySomething();
}
}
[TestMethod]
public void T()
{
// first test
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsClosedTypesOf(typeof(IExample<>));
var mockA = new Moq.Mock<IExampleDependecy>();
mockA.Setup(d => d.SaySomething()).Callback(() => Console.WriteLine("A"));
builder.RegisterInstance(mockA.Object).SingleInstance();
var container = builder.Build();
var sample1 = container.Resolve<IExample<string>>();
sample1.Hello();
// new test using same container
var updater = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsClosedTypesOf(typeof(IExample<>));
var mockB = new Moq.Mock<IExampleDependecy>();
mockB.Setup(d => d.SaySomething()).Callback(() => Console.WriteLine("B"));
builder.RegisterInstance(mockB.Object).SingleInstance();
updater.Update(container); // overwrites existing registrations
var sample2 = container.Resolve<IExample<string>>();
sample2.Hello();
}
and result is:
Hello
A
Hello
A
Autofac by default overrides previous registrations with subsequent ones. That means you don't have to do anything special apart from updating container with your new instance:
var builder = new ContainerBuilder();
builder.RegisterInstance(new Sample("A")).SingleInstance();
var container = builder.Build();
var sample = container.Resolve<Sample>();
// do your test
...
// new test using same container
var updater = new ContainerBuilder();
updater.RegisterInstance(new Sample("B")).SingleInstance();
updater.Update(container); // overwrites existing registrations
var sample = container.Resolve<Sample>(); // returns "B"

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.