What unit tests would be better or redudant - c#

I started to learn a new topic for me - unit testing. Ffter writing a few tests, I began to wonder what else can be written and whether it will not be redundant. If you write code examples it would be even better for me, thank you.
For example do i need to write same Assert.ThrowsAsync<NullReferenceException> test for method ChooseFilmToWatchWithChilds or it will be redundant
/// <summary>
/// Check if database returns null
/// </summary>
[Fact]
public void NoFilmsInDatabase_ExceptionReturns()
{
Assert.ThrowsAsync<NullReferenceException>(
async () => { await _homeFilmHandler.IndicateFavouritesOfOurFilms(); });
}
Like this
/// <summary>
/// Check if database returns null
/// </summary>
[Fact]
public void NoFilmsInDatabaseChooseFilmToWatchWithChilds_ExceptionReturns()
{
Assert.ThrowsAsync<NullReferenceException>(
async () => { await _homeFilmHandler.ChooseFilmToWatchWithChilds(); });
}
Implementation class
using VideoArchive.Contracts.Abstractions.Data;
using VideoArchive.Contracts.Abstractions.FilmServices;
using VideoArchive.Contracts.Abstractions.Integrations;
using VideoArchive.Contracts.Models;
using VideoArchive.Contracts.Models.Home;
namespace VideoArchive.Implementations.FilmServices
{
public class HomeFilmHandler : IHomeFilmHandler
{
#region Constructors and DI
private readonly IFakeDatabaseService _fakeDatabaseService;
private readonly IFakeInegrationService _fakeInegrationService;
private readonly IMapper _mapper;
public HomeFilmHandler(
IFakeDatabaseService fakeDatabaseService,
IFakeInegrationService fakeInegrationService,
IMapper mapper)
{
_fakeDatabaseService = fakeDatabaseService;
_fakeInegrationService = fakeInegrationService;
_mapper = mapper;
}
#endregion
public async Task<HomeFilm> ChooseFilmToWatchWithChilds()
{
var allFilms = await _fakeDatabaseService.GetAllFilms();
var filmToWatch = allFilms
.Where(film => film.IsFavorite)
.OrderByDescending(film => film.TimesWatched);
foreach (var film in filmToWatch)
{
if (_fakeInegrationService.GetAgeRestrictions(_mapper.Map<Film>(film)) <= 6)
{
return film;
}
}
return await Task.FromResult<HomeFilm>(null);
}
public async Task IndicateFavouritesOfOurFilms()
{
var allFilms = await _fakeDatabaseService.GetAllFilms();
if (!allFilms.Any() || allFilms == null)
{
throw new NullReferenceException();
}
IndicateFilms(allFilms);
}
private async void IndicateFilms(List<HomeFilm> films)
{
foreach (var film in films)
{
if (film.TimesWatched >= 5)
{
film.IsFavorite = true;
await _fakeDatabaseService.UpdateFilm(film, film.Id);
}
}
}
}
}
TestsClass
using AutoMapper;
using VideoArchive.Contracts.Abstractions.Data;
using VideoArchive.Contracts.Abstractions.Integrations;
using VideoArchive.Contracts.Models.Home;
using VideoArchive.Implementations.FilmServices;
using Moq;
using Xunit;
namespace VideoArchive.Tests
{
public class HomeFilmHandlerTests
{
private readonly Fixture _fixture = new Fixture();
private readonly Mock<IFakeDatabaseService> _fakeDatabaseService = new Mock<IFakeDatabaseService>();
private readonly Mock<IFakeInegrationService> _fakeIntegrationService = new Mock<IFakeInegrationService>();
private readonly Mock<IMapper> _mapper = new Mock<IMapper>();
private readonly List<HomeFilm> _homeFilms;
private readonly HomeFilmHandler _homeFilmHandler;
public HomeFilmHandlerTests()
{
_homeFilms = _fixture.CreateMany<HomeFilm>().ToList();
_fakeDatabaseService
.Setup(service => service
.GetAllFilms())
.ReturnsAsync(_homeFilms);
_fakeDatabaseService
.Setup(service => service
.UpdateFilm(It.IsAny<HomeFilm>(), It.IsAny<int>()));
_fakeIntegrationService
.Setup(service => service
.GetAgeRestrictions(It.IsAny<HomeFilm>()));
_homeFilmHandler = new HomeFilmHandler(
_fakeDatabaseService.Object,
_fakeIntegrationService.Object,
_mapper.Object);
}
[Fact]
public void NoFilmsInDatabase_ExceptionReturns()
{
Assert.ThrowsAsync<NullReferenceException>(async () => { await _homeFilmHandler.IndicateFavouritesOfOurFilms(); });
}
[Fact]
public async void FilmsAreIndicatedRightTimes_UpdateFilmCalled()
{
var filmCountThatNeededToIndicate =
_homeFilms.Where(film => film.TimesWatched >= 5).Count();
await _homeFilmHandler.IndicateFavouritesOfOurFilms();
_fakeDatabaseService.Verify(
service => service.UpdateFilm(It.IsAny<HomeFilm>(), It.IsAny<int>()),
Times.Exactly(filmCountThatNeededToIndicate));
}
[Fact]
public void FilmWithChild()
{
//Write test here
}
}
}
FakeDatabaseService
using AutoFixture;
using VideoArchive.Contracts.Abstractions.Data;
using VideoArchive.Contracts.Models.Home;
namespace VideoArchive.Implementations.Data.Fake
{
/// <summary>
/// Fake service imitating databaseService
/// </summary>
public class FakeDatabaseService : IFakeDatabaseService
{
private readonly Fixture Fixture = new Fixture();
public async Task<List<HomeFilm>> GetAllFilms()
{
return Fixture.CreateMany<HomeFilm>().ToList();
}
public async Task<HomeFilm> GetFilmById(int id)
{
return Fixture.Build<HomeFilm>().With(film => film.Id, id).Create();
}
public async Task UpdateFilm(HomeFilm film, int id)
{
// Logic to updating film
}
}
}
FakeInegrationService
namespace VideoArchive.Contracts.Abstractions.Integrations
{
public interface IFakeIntegrationService
{
public int GetAgeRestrictions(IFilm film);
}
}

Testing that both method throw some exception is not redundant.
Each method has its own behavior. The fact that they behave similarly doesn't change that.
What to test next? I suggest you check Microsoft's Unit testing best practices for some insights.
However, if you feel that your tests are redundant, it could be a sign that your architecture can be improved.
Example:
If you change the constructor of HomeFilmHandler so that it throws an ArgumentNullException when its fakeDatabaseService parameter is null, you could replace the 2 tests that checks the NullReferenceException by 1 test that checks that the constructor of HomeFilmHandler throws when the fakeDatabaseService is null. This is just an example to illustrate the point.

Related

How to inject depdency while mocking protected method using AutoMock XNnit?

In the below code snippet, I am trying to mock the response of a protected method and test the response of the public method which calls the protected method. The protected method calls an external API, hence I would like to mock the response in order to test the public method which calls the protected method.
The problem arises when I try to inject the dependency on the class which is used by the public method. It always goes to the default constructor ignoring the dependency objects I pass. Please let me know where I am missing the change.
Note: I have added the default constructor just to understand the flow. I will not have it in the real implementation.
Test Method
[Fact]
public void Test()
{
using (var mock = AutoMock.GetLoose())
{
Depdency1 depdency1 = new Depdency1();
Depdency2 depdency2 = new Depdency2();
var parm1 = new NamedParameter("dependency1", depdency1);
var parm2 = new NamedParameter("dependency2", depdency2);
//Mock the protected method
mock.Mock<SystemUnderTest>(parm1, parm2)
.Protected()
.Setup<bool>("IsOrderValid", "TestOrder")
.Returns(true);
var sut = mock.Create<SystemUnderTest>();
sut.ProcessOrder("Test");
}
}
Main class
public class SystemUnderTest : ISystemUnderTest
{
private readonly IDependency1 _dependency1;
private readonly IDependency2 _dependency2;
public SystemUnderTest()
{
}
public SystemUnderTest(IDependency1 dependency1, IDependency2 dependency2)
{
_dependency1 = dependency1;
_dependency2 = dependency2;
}
public bool ProcessOrder(string OrderID)
{
//Businss logic using dependency1
if (IsOrderValid(OrderID))
{
if (_dependency1 == null)
{
throw new AggregateException("Depdency1 is null");
}
//Businss logic
return true;
}
else
{
if (_dependency1 == null)
{
throw new AggregateException("Depdency1 is null");
}
//Businss logic
return false;
}
}
protected virtual bool IsOrderValid(string OrderID)
{
//Business logic using dependency2
if (_dependency2 == null)
{
throw new AggregateException("Depdency2 is null");
}
return true; //False based on logic
}
}
public interface IDependency1
{
void Method1();
}
public interface IDependency2
{
void Method2();
}
public class Depdency1 : IDependency1
{
private int _property1;
public void Method1()
{
throw new NotImplementedException();
}
}
public class Depdency2 : IDependency2
{
private int _property2;
public void Method2()
{
throw new NotImplementedException();
}
}

Testing Code with Third Party Object Instantiation

New to unit testing and trying to get my head around some simple tests for a piece of code which gets or creates a template if it doesn't exist (in Umbraco 8).
The method is quite simple, when Initialise is called, it gets the template and if it doesn't exist, creates it:
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Papermoon.Umbraco.Aldus.Core.Components
{
public class TemplateComponent : IComponent
{
private readonly IFileService _fileService;
public TemplateComponent(IFileService fileService)
{
_fileService = fileService;
}
public void Initialize()
{
ITemplate blogTemplate = _fileService.GetTemplate("aldusBlog");
if (blogTemplate == null)
{
blogTemplate = new Template("Aldus Blog", "aldusBlog");
_fileService.SaveTemplate(blogTemplate);
}
}
public void Terminate() { }
}
}
Works okay, no problem.
I'm trying to write a few tests, the first checks if _fileService.GetTemplate is called.
The second test should check that _fileService.SaveTemplate() is called if that returns null.
using Moq;
using NUnit.Framework;
using Papermoon.Umbraco.Aldus.Core.Components;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Papermoon.Umbraco.Aldus.Core.Tests.Components
{
[TestFixture]
public class TemplateComponentTests
{
private Mock<IFileService> _fileService;
private TemplateComponent _component;
[SetUp]
public void SetUp()
{
_fileService = new Mock<IFileService>();
_component = new TemplateComponent(_fileService.Object);
}
[Test]
public void Initialise_WhenCalled_GetsBlogTemplate()
{
_component.Initialize();
_fileService.Verify(s => s.GetTemplate("aldusBlog"), Times.Once);
}
[Test]
public void Initialise_BlogTemplateDoesNotExist_CreateTemplate()
{
_fileService
.Setup(s => s.GetTemplate("aldusBlog"))
.Returns((ITemplate) null);
_component.Initialize();
_fileService.Verify(s => s.SaveTemplate(It.Is<ITemplate>(p => p.Alias == "aldusBlog"), -1), Times.Once());
}
}
}
The trouble when I do this is that the blogTemplate = new Template("Aldus Blog", "aldusBlog"); throws an error:
Can not get Current.Config during composition. Use composition.Config.
I assume this is because I don't have any kind of context which leads me to think that the ITemplate needs to be mocked. However, because new Template("Aldus Blog", "aldusBlog"); will always be called, it will always throw this error.
Obviously the code isn't bullet proof, so how do I refactor this to be testable?
That 3rd party class is probably tightly coupled to an implementation concern that does not exist or is not configured when unit testing in isolation.
abstract that object creation out into a factory.
public interface ITemplateFactory {
ITemplate Create(string name, string alias);
}
whose implementation can be injected at run-time
public class DefaultTemplateFactory : ITemplateFactory {
public ITemplate Create(string name, string alias) {
return new Template(name, alias);
}
}
Provided it is registered at the composition root during startup
This now allow the component to be loosely coupled away from implementation concerns
public class TemplateComponent : IComponent {
private readonly IFileService fileService;
private readonly ITemplateFactory templateFactory;
public TemplateComponent(IFileService fileService, ITemplateFactory templateFactory) {
this.fileService = fileService;
this.templateFactory = templateFactory;
}
public void Initialize() {
ITemplate blogTemplate = fileService.GetTemplate("aldusBlog");
if (blogTemplate == null) {
blogTemplate = templateFactory.Create("Aldus Blog", "aldusBlog");
fileService.SaveTemplate(blogTemplate);
}
}
public void Terminate() { }
}
That can be replaced as needed when testing in isolation
[TestFixture]
public class TemplateComponentTests {
private Mock<IFileService> fileService;
private Mock<ITemplateFactory> templateFactory;
private TemplateComponent component;
string templateAlias = "aldusBlog";
[SetUp]
public void SetUp() {
//Arrange
fileService = new Mock<IFileService>();
templateFactory = new Mock<ITemplateFactory>();
templateFactory.Setup(_ => _.Create(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string name, string alias) =>
Mock.Of<ITemplate>(_ => _.Alias == alias && _.Name == name)
);
component = new TemplateComponent(fileService.Object, templateFactory.Object);
}
[Test]
public void Initialise_WhenCalled_GetsBlogTemplate() {
//Act
component.Initialize();
//Assert
fileService.Verify(s => s.GetTemplate(templateAlias), Times.Once);
}
[Test]
public void Initialise_BlogTemplateDoesNotExist_CreateTemplate() {
//Act
component.Initialize();
//Assert
fileService.Verify(s => s.SaveTemplate(It.Is<ITemplate>(p => p.Alias == templateAlias), 0), Times.Once());
}
}

ChangeToken.OnChange not fire on custom configuration provider

I'll try to create custom config provider that will take keys from database. As written in manuals, I created this provider and it's work fine. All keys are loaded on start and all works fine.
But now I'm trying to use IOptionsSnapshot and reload keys from db as they change. But nothing happens.
Can anyone tell me what's going wrong? Here is my code:
public class EFConfigProvider : ConfigurationProvider
{
private DateTime lastLoaded;
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
lastLoaded = DateTime.Now;
ChangeToken.OnChange(
() => Watch(),
() => {
Thread.Sleep(250);
this.Load();
});
}
public new IChangeToken GetReloadToken()
{
return Watch();
}
Action<DbContextOptionsBuilder> OptionsAction { get; }
// Load config data from EF DB.
public override void Load()
{
this.Data.Clear();
var builder = new DbContextOptionsBuilder<ConfigContext>();
OptionsAction(builder);
using (var dbContext = new ConfigContext(builder.Options))
{
// Save Load Fact
dbContext.SaveLoadFact();
// Load Partners Settings
GetPartners(dbContext);
}
}
private IChangeToken Watch()
{
return new DatabaseChangeToken();
}
}
public class DatabaseChangeToken : IChangeToken
{
public bool HasChanged
{
get
{
return true;
}
}
public bool ActiveChangeCallbacks => false;
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => EmptyDisposable.Instance;
internal class EmptyDisposable : IDisposable
{
public static EmptyDisposable Instance { get; } = new EmptyDisposable();
private EmptyDisposable() { }
public void Dispose() { }
}
}
What I did to start to worked it:
I add in class EFConfigProvider variable
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
I add in constructor
// Start Periodic task to refresh the DB
PeriodicTask.Run(() =>
{
//Refresh();
OnReload();
}, TimeSpan.FromSeconds(reload));
I Add class for periodTask
public class PeriodicTask
{
public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(period, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
action();
}
}
public static Task Run(Action action, TimeSpan period)
{
return Run(action, period, CancellationToken.None);
}
}
Add method on Reload
protected new void OnReload()
{
var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
Add change of using change token
ChangeToken.OnChange(
() => { return this._reloadToken; },
() => {
Thread.Sleep(250);
this.Load();
});
Updated: I found that overriding OnReload() broke the notification of changes (I was using OptionsMonitor, which didn't see the changes). So I changed my implementation to not do that. It's calling the default OnReload() which handles the notification of changes to OptionsMonitor.
Your updated implementation really helped me out, so thank you!
But rather than using your custom PeriodicTask, you could just use a Timer. The effect is just the same though.
Here is my implementation, which reloads the data every 5 minutes:
using System;
using System.Linq;
using System.Threading;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using Timer = System.Timers.Timer;
namespace MyProject.Classes.Configuration {
public class MyConfigProvider : ConfigurationProvider {
private readonly DbContextOptions<MyDbContext> _dbOptions;
private readonly Timer _reloadTimer = new Timer();
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
public MyConfigProvider(Action<DbContextOptionsBuilder> dbOptionsAction) {
var builder = new DbContextOptionsBuilder<MyDbContext>();
dbOptionsAction(builder);
_dbOptions = builder.Options;
_reloadTimer.AutoReset = false;
_reloadTimer.Interval = TimeSpan.FromMinutes(5).TotalMilliseconds;
_reloadTimer.Elapsed += (s, e) => { Load(); };
}
public override void Load() {
try {
using (var db = new MyDbContext(_dbOptions)) {
var settings = db.Settings.AsNoTracking().ToList();
Data.Clear();
foreach (var s in settings) {
Data.Add(s.Name, s.Value);
}
}
OnReload();
} finally {
_reloadTimer.Start();
}
}
}
}
I am having same requirement and I reached to you code and I tried the same thing and not working.
But your code and some other investigation lead me to the hint.
public class CustomConfigurationProvider : ConfigurationProvider
{
private readonly string applicationName;
private readonly bool reloadOnChange;
private readonly IConfiguration configuration;
public CustomConfigurationProvider(string applicationName, bool reloadOnChange)
{
this.applicationName = applicationName;
this.reloadOnChange = reloadOnChange;
if(reloadOnChange)
{
ChangeToken.OnChange(
() => GetReloadToken(), // listener to token change
() =>
{
Thread.Sleep(250);
this.Load();
});
}
}
public override async void Load()
{
Data.Clear();
Data = read data from database;
if (Condition to check if data in database changed)
{
OnReload(); // This will create new token and trigger change so what is register in OnChange above will be called again which is this.Load()
}
}
}
I also refereed https://www.mikesdotnetting.com/article/301/loading-asp-net-core-mvc-views-from-a-database-or-other-location
and
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/primitives/change-tokens
Hope this helps.

Test Environment.Exit() in C#

Is there in C# some kind of equivalent of ExpectedSystemExit in Java? I have an exit in my code and would really like to be able to test it. The only thing I found in C# is a not really nice workaround.
Example Code
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
Test Code
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.UserHasRights().Returns(false);
???
}
I am using the VS framework for testing (+ NSubstitute for mocking) but it is not a problem to switch to nunit or whatever for this test.
You should use dependency injection to supply to the class being tested an interface that provides an environmental exit.
For example:
public interface IEnvironment
{
void Exit(int code);
}
Let's also assume that you have an interface for calling UserHasRights():
public interface IRightsService
{
bool UserHasRights();
}
Now suppose your class to be tested looks like this:
public sealed class RightsChecker
{
readonly IRightsService service;
readonly IEnvironment environment;
public RightsChecker(IRightsService service, IEnvironment environment)
{
this.service = service;
this.environment = environment;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
environment.Exit(1);
}
}
}
Now you can use a mocking framework to check that IEnvironment .Exit() is called under the right conditions. For example, using Moq it might look a bit like this:
[TestMethod]
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<IEnvironment>();
var rightsChecker = new RightsChecker(rightsService.Object, enviromnent.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
Ambient contexts and cross-cutting concerns
A method such as Environment.Exit() could be considered to be a cross-cutting concern, and you might well want to avoid passing around an interface for it because you can end up with an explosion of additional constructor parameters. (Note: The canonical example of a cross cutting concern is DateTime.Now.)
To address this issue, you can introduce an "Ambient context" - a pattern which allows you to use a static method while still retaining the ability to unit test calls to it. Of course, such things should be used sparingly and only for true cross-cutting concerns.
For example, you could introduce an ambient context for Environment like so:
public abstract class EnvironmentControl
{
public static EnvironmentControl Current
{
get
{
return _current;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
_current = value;
}
}
public abstract void Exit(int value);
public static void ResetToDefault()
{
_current = DefaultEnvironmentControl.Instance;
}
static EnvironmentControl _current = DefaultEnvironmentControl.Instance;
}
public class DefaultEnvironmentControl : EnvironmentControl
{
public override void Exit(int value)
{
Environment.Exit(value);
}
public static DefaultEnvironmentControl Instance => _instance.Value;
static readonly Lazy<DefaultEnvironmentControl> _instance = new Lazy<DefaultEnvironmentControl>(() => new DefaultEnvironmentControl());
}
Normal code just calls EnvironmentControl.Current.Exit(). With this change, the IEnvironment parameter disappears from the RightsChecker class:
public sealed class RightsChecker
{
readonly IRightsService service;
public RightsChecker(IRightsService service)
{
this.service = service;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
EnvironmentControl.Current.Exit(1);
}
}
}
But we still retain the ability to unit-test that it has been called:
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<EnvironmentControl>();
EnvironmentControl.Current = enviromnent.Object;
try
{
var rightsChecker = new RightsChecker(rightsService.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
finally
{
EnvironmentControl.ResetToDefault();
}
}
For more information about ambient contexts, see here.
I ended up creating a new method which I can then mock in my tests.
Code
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
internal virtual void Exit()
{
Environment.Exit(1);
}
Unit test
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.When(svc => svc.Exit()).DoNotCallBase();
...
service.CheckRights();
service.Received(1).Exit();
}
If your goal is to avoid extra classes/interfaces just to support tests, how do you feel about Environment.Exit action via Property Injection?
class RightsChecker
{
public Action AccessDeniedAction { get; set; }
public RightsChecker(...)
{
...
AccessDeniedAction = () => Environment.Exit();
}
}
[Test]
public TestCheckRightsWithoutRights()
{
...
bool wasAccessDeniedActionExecuted = false;
rightsChecker.AccessDeniedAction = () => { wasAccessDeniedActionExecuted = true; }
...
Assert.That(wasAccessDeniedActionExecuted , Is.True);
}

C# Static Readonly log4net logger, any way to change logger in Unit Test?

My class has this line:
private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
When I go to unit test, I can't inject a moq logger into this interface so I could count log calls.
Is there a way to do this? Log4net recommends static readonly pattern for loggers. What's best way to handle it?
While log4net recommends this pattern, nothing prevents you from instantiating the logger outside the class, and inject it. Most of the IoCs can be configured to inject one and the same instance. That way, for your unit tests you can inject a mock.
I would recommend a wrapper around LogManager.GetLogger, which returns always one and the same logger instance per type:
namespace StackOverflowExample.Moq
{
public interface ILogCreator
{
ILog GetTypeLogger<T>() where T : class;
}
public class LogCreator : ILogCreator
{
private static readonly IDictionary<Type, ILog> loggers = new Dictionary<Type, ILog>();
private static readonly object lockObject;
public ILog GetTypeLogger<T>() where T : class
{
var loggerType = typeof (T);
if (loggers.ContainsKey(loggerType))
{
return loggers[typeof (T)];
}
lock (lockObject)
{
if (loggers.ContainsKey(loggerType))
{
return loggers[typeof(T)];
}
var logger = LogManager.GetLogger(loggerType);
loggers[loggerType] = logger;
return logger;
}
}
}
public class ClassWithLogger
{
private readonly ILog logger;
public ClassWithLogger(ILogCreator logCreator)
{
logger = logCreator.GetTypeLogger<ClassWithLogger>();
}
public void DoSomething()
{
logger.Debug("called");
}
}
[TestFixture]
public class Log4Net
{
[Test]
public void DoSomething_should_write_in_debug_logger()
{
//arrange
var logger = new Mock<ILog>();
var loggerCreator = Mock.Of<ILogCreator>(
c =>
c.GetTypeLogger<ClassWithLogger>() == logger.Object);
var sut = new ClassWithLogger(loggerCreator);
//act
sut.DoSomething();
//assert
logger.Verify(l=>l.Debug("called"), Times.Once());
}
}
}
You can use MemoryAppender instead a mocked logger. By this approach, you can configure log4net to collect the log events in memory and get them by GetEvents() to verify.
I found the these useful examples:
https://gist.github.com/plaurin/3563082#file-trycache-cs
http://jake.ginnivan.net/verifying-logged-messages-with-log4net/
Here is the first:
[SetUp]
public void SetUp()
{
this.memoryAppender = new MemoryAppender();
BasicConfigurator.Configure(this.memoryAppender);
...
}
[Test]
public void TestSomething()
{
...
// Assert
Assert.IsFalse(
this.memoryAppender.GetEvents().Any(le => le.Level == Level.Error),
"Did not expect any error messages in the logs");
}
And the more detailed:
public static class Log4NetTestHelper
{
public static string[] RecordLog(Action action)
{
if (!LogManager.GetRepository().Configured)
BasicConfigurator.Configure();
var logMessages = new List<string>();
var root = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
var attachable = root as IAppenderAttachable;
var appender = new MemoryAppender();
if (attachable != null)
attachable.AddAppender(appender);
try
{
action();
}
finally
{
var loggingEvents = appender.GetEvents();
foreach (var loggingEvent in loggingEvents)
{
var stringWriter = new StringWriter();
loggingEvent.WriteRenderedMessage(stringWriter);
logMessages.Add(string.Format("{0} - {1} | {2}", loggingEvent.Level.DisplayName, loggingEvent.LoggerName, stringWriter.ToString()));
}
if (attachable != null)
attachable.RemoveAppender(appender);
}
return logMessages.ToArray();
}
}
Just take the readonly away, the Logger will work as well as a usual interface object.
See the following simple example being tested:
using System;
using log4net;
namespace StackOverflowExample.Moq
{
public class ClassWithLogger
{
private static ILog _log;
//private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
public ClassWithLogger(ILog log)
{
_log = log;
}
public void DoSomething(object para = null)
{
try
{
if(para != null)
{
_log.Debug("called");
}
else
{
throw new System.ArgumentException("Parameter cannot be null");
}
}
catch (Exception ex)
{
_log.Fatal("Exception raised!", ex);
}
}
}
}
Unit Test Code:
[TestMethod]
public void Test_DoSomething_logger()
{
//arrange
var mockLog = new Mock<log4net.ILog>();
var classWithLogger = new ClassWithLogger(mockLog.Object);
mockLog.Setup(m => m.Debug(It.IsAny<string>()));
mockLog.Setup(m => m.Fatal(It.IsAny<string>(), It.IsAny<Exception>()));
//act1
classWithLogger.DoSomething(new object());
//assert1
mockLog.Verify(l => l.Debug("called"), Times.Once());
//act2
classWithLogger.DoSomething();
//assert2
mockLog.Verify(x => x.Fatal("Exception raised!", It.IsAny<Exception>()));
}
And main program call:
public void MainProgramCall()
{
//......
ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
var classWithLogger = new ClassWithLogger(log);
classWithLogger.DoSomething(new object());
//......
classWithLogger.DoSomething();
//......
}

Categories

Resources