1

Consider a method with a try catch like this:

try
{
    person = this.personRepo.GetPerson(name);
}
catch (PersonException)
{
    LogHelper.LogDebug("PersonService", "No Person found!");
}

In the unit test, the personRepo is faked with FakeItEasy:

A.CallTo(() => this.personRepository.GetPerson(personName)).Throws(new PersonException("Person not found"));

Question:

How can I check if the static logger was called?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
xeraphim
  • 4,375
  • 9
  • 54
  • 102
  • Because `LogHelper.LogDebug` is static method - you can not mock it. So you need to test actual behaviour of `LogDebug`. If it writes to the file - read file, if ti write to database - read database. If `LogHelper` configurable - you can configure it to rite to the output which can be easily tested (MemoryStream for example). But be ware, the it is static method and if you run tests in parallel - other tests will be affected by same configuration. Proper testable solution - introduce abstraction of "logger" and inject it to the class. Actual implementation can still call static method. – Fabio Oct 17 '17 at 06:34
  • This is usually rather complicated. That is also one reason for avoiding statics. You can use MS Fakes (Shim Types) but AFAIK you will need Visual Studio Enterprise (or previously ultimate) for it. Otherwise you may apply refactoring. If dependency injection is no choice for you, you may, as a first shot, introduce a singleton ``LogHelper.Instance.LogDebug(...)`` which can be set in a unit-test. – Peit Oct 17 '17 at 06:37
  • You can try to test, if exception happened. But at the end of the test you should delete data, that were saved. Not sure if it is a best approach. – Sasha Oct 17 '17 at 06:56

1 Answers1

8

The assumption here is that the personRepo is injected into the subject under test. Thus I also assume you are using DI.

The currently displayed code is tightly coupled to static implementation concerns which make it difficult to test the subject in isolation.

To make the subject more flexible and easier to maintain and test in isolation, the subject would need to be refactored to depend on an abstraction and not on a concretion.

Encapsulate the static LogHelper behind an abstraction.

public interface ILogger {
    void LogDebug(string category, string msg);
    //...other members
}

which would wrap the desired behavior/functionality

public class LogHelperWrapper : ILogger {
    public void LogDebug(string source, string msg) {
        LogHelper.LogDebug(source, msg);
    }

    //...other members
}

The subject would make use of the abstraction, which would have refactored to follow explicit dependency principle via constructor injection.

private ILogger log; //set via constructor injection
try {
    person = this.personRepo.GetPerson(name);
} catch (PersonException) {
    this.log.LogDebug("PersonService", "No Person found!");
}

This will allow the subject under test to receive a replacement when tested in isolation.

//Arrange
var logger = A.Fake<ILogger>();
//...other arrangements

//Act
//...exercise test

//Assertion
A.CallTo(() => logger.LogDebug("PersonService", "No Person found!"))
.MustHaveHappened(Repeated.Exactly.Once);
//...other assertions.
Nkosi
  • 235,767
  • 35
  • 427
  • 472