Cannot test ILogger<T> Received with NSubstitute - unit-testing

I have a .Net Core 3 application and am trying to test calls to ILogger in my method:
public class MyClass
{
private readonly ILogger<MyClass> _logger;
public MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
public void MyMethod(string message)
{
_logger.LogError(message);
}
}
Having found answers here on SO and on blogs, I know that I have to test against the interface method, not the extension method, so I have this test:
[TestMethod]
public void MyMethodTest()
{
// Arrange
var logger = Substitute.For<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
// Assert
logger.Received(1).Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString() == message),
null,
Arg.Any<Func<object, Exception, string>>());
}
However, this isn't working and I get this error:
Test method MyLibrary.Tests.MyClassTests.MyMethodTest threw exception:
NSubstitute.Exceptions.ReceivedCallsException: Expected to receive exactly 1 call matching:
Log<Object>(Error, any EventId, o => (o.ToString() == value(MyLibrary.Tests.MyClassTests+<>c__DisplayClass0_0).message), <null>, any Func<Object, Exception, String>)
Actually received no matching calls.
at NSubstitute.Core.ReceivedCallsExceptionThrower.Throw(ICallSpecification callSpecification, IEnumerable`1 matchingCalls, IEnumerable`1 nonMatchingCalls, Quantity requiredQuantity)
at NSubstitute.Routing.Handlers.CheckReceivedCallsHandler.Handle(ICall call)
at NSubstitute.Routing.Route.Handle(ICall call)
at NSubstitute.Core.CallRouter.Route(ICall call)
at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at NSubstitute.Proxies.CastleDynamicProxy.ProxyIdInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ObjectProxy.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)
at MyLibrary.Tests.MyClassTests.MyMethodTest() in D:\Source\Scratch\MyLibrary\MyLibrary.Tests\MyClassTests.cs:line 25
What am I doing wrong?
netcoreapp3.0 / Microsoft.Extensions.Logging 3.1.2 / NSubstitute 4.2.1
UPDATE: I have tried the match with Arg.Any<>() and get the same result:
logger.Received(1).Log(
Arg.Any<LogLevel>(),
Arg.Any<EventId>(),
Arg.Any<object>(),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
UPDATE 2: I have tried the same test using Moq and get the same result:
logger.Verify(l => l.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.Is<object>(o => o.ToString() == message),
null,
It.IsAny<Func<object, Exception, string>>()),
Times.Once);
Result:
Test method MyLibrary.Tests.Moq.MyClassTests.MyMethodTest threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: l => l.Log<object>(LogLevel.Error, It.IsAny<EventId>(), It.Is<object>(o => o.ToString() == "a message"), null, It.IsAny<Func<object, Exception, string>>())
Performed invocations:
Mock<ILogger<MyClass>:1> (l):
ILogger.Log<FormattedLogValues>(LogLevel.Error, 0, a message, null, Func<FormattedLogValues, Exception, string>)
at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
at Moq.Mock`1.Verify(Expression`1 expression, Times times)
at Moq.Mock`1.Verify(Expression`1 expression, Func`1 times)
at MyLibrary.Tests.Moq.MyClassTests.MyMethodTest() in D:\Source\Scratch\MyLibrary\MyLibrary.Tests.Moq\MyClassTests.cs:line 25

The main issue unit testing ILogger invocations with .NET Core 3.* is that FormattedLogValues was changed to internal, it complicates things.
The Moq workaround is to use It.IsAnyType:
public class TestsUsingMoq
{
[Test]
public void MyMethod_String_LogsError()
{
// Arrange
var logger = Mock.Of<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
//Assert
Mock.Get(logger)
.Verify(l => l.Log(LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => ((IReadOnlyList<KeyValuePair<string, object>>) o).Last().Value.ToString().Equals(message)),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
Times.Once);
}
}
NSubstitute doesn't have an It.IsAnyType equivalent at the moment as far as I am aware, which presents an issue when trying to use the Received method. There is a workaround however as it does provide a ReceivedCalls method which you can iterate over and do you own invocation check.
public class TestsUsingNSubstitute
{
[Test]
public void MyMethod_String_LogsError()
{
// Arrange
var logger = Substitute.For<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
//Assert
Assert.That(logger.ReceivedCalls()
.Select(call => call.GetArguments())
.Count(callArguments => ((LogLevel) callArguments[0]).Equals(LogLevel.Error) &&
((IReadOnlyList<KeyValuePair<string, object>>) callArguments[2]).Last().Value.ToString().Equals(message)),
Is.EqualTo(1));
}
}
As a workaround, it's not a bad one, and could be easily bundled up into an extension method.
FormattedLogValues implements IReadOnlyList<KeyValuePair<string, object>>. The last item in this list is the original message that you specified.
Working sample

I tried wrapping the Logger around an adapter, more like a proxy class.
You can mock the adapter interface and that would return what log function has been called.
public class LoggerAdapter<TType> : ILoggerAdapter<TType>
{
private readonly ILogger<TType> _logger;
public LoggerAdapter(ILogger<TType> logger)
{
_logger = logger;
}
public void LogInformation(string message, params object[] args)
{
_logger.LogInformation(message,args);
}
public void LogError(string message, params object[] args)
{
_logger.LogError(message, args);
}
}
private readonly ILoggerAdapter<PosUpdateService> _logger = Substitute.For<ILoggerAdapter<PosUpdateService>>();
_logger.Received(1).LogInformation("User with Id {id} was fetched in {0} milliseconds",
Arg.Is(Id),
Arg.Any<long>());

Related

Call to FakeItEasy mocked mediatr.send method fails assertion

I am new to FakeItEasy and I am having a problem asserting if an async method has been called. It fails assertion because it hasn't been called. I have done my best to ensure the assertion matches the configured call but still no dice.
[HttpPost]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post(CreateOwnerRequest requestModel)
{
var command = new CreateOwnerCommand { RequestModel = requestModel };
var ownerResponse = await _mediator.Send(command, default);
//TODO: return URI for the new resources
return Created("", ownerResponse);
}
[Theory]
[ClassData(typeof(ValidCreateOwnerTestData))]
public async void ShouldCallCreateOwnerHandler(CreateOwnerRequest validCreateOwnerModel)
{
// Arrange
var fakeMediator = A.Fake<IMediator>();
A.CallTo(() => fakeMediator.Send(new CreateOwnerCommand { RequestModel = validCreateOwnerModel },
default)).Returns(A.Dummy<Task<OwnerResponse>>());
var ownerController = new OwnerController(fakeMediator);
// Act
_ = await ownerController.Post(validCreateOwnerModel);
// Assert
A.CallTo(() => fakeMediator.Send(new CreateOwnerCommand { RequestModel = validCreateOwnerModel },
default)).MustHaveHappened();
}
public class ValidCreateOwnerTestData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { new CreateOwnerRequest { FirstName = "Foo", LastName = "Bar" } };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
In your assertion, you create a new instance of CreateOwnerRequest. Obviously, this isn't the same instance that is actually used in the controller action, so it isn't considered equal, and the assertion fail.
You could override Equals on CreateOwnerRequest so that they are considered equal, but it's probably not a good idea, since you would do it only in order to satisfy the assertion.
A better approach is to use argument constraints to specify a condition that the argument must match:
// Assert
A.CallTo(() => fakeMediator.Send(
A<CreateOwnerCommand>.That.Matches(command => /* some condition here */))
A<CancellationToken>.Ignored))
.MustHaveHappened();
(note that I also changed default to A<CancellationToken>.Ignored, because you probably don't want the assertion to fail if the controller action starts using a real cancellation token...)

Spring-Webflux: Handler function unit test is throwing UnsupportedMediaTypeStatusException

I am trying to write Unit test to the handler function, I followed the example from the Spring project. Can someone help me why the following test is throwing UnsupportedMediaTypeStatusException?
Thanks
Handler function
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
log.info("{} Processing create request", serverRequest.exchange().getLogPrefix());
return ok().body(serverRequest.bodyToMono(Person.class).map(p -> p.toBuilder().id(UUID.randomUUID().toString()).build()), Person.class);
}
Test Class
#SpringBootTest
#RunWith(SpringRunner.class)
public class MyHandlerTest {
#Autowired
private MyHandler myHandler;
private ServerResponse.Context context;
#Before
public void createContext() {
HandlerStrategies strategies = HandlerStrategies.withDefaults();
context = new ServerResponse.Context() {
#Override
public List<HttpMessageWriter<?>> messageWriters() {
return strategies.messageWriters();
}
#Override
public List<ViewResolver> viewResolvers() {
return strategies.viewResolvers();
}
};
}
#Test
public void handle() {
Gson gson = new Gson();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create")
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
MockServerHttpResponse mockResponse = exchange.getResponse();
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<ServerResponse> serverResponseMono = myHandler.handle(serverRequest);
Mono<Void> voidMono = serverResponseMono.flatMap(response -> {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
boolean condition = response instanceof EntityResponse;
assertThat(condition).isTrue();
return response.writeTo(exchange, context);
});
StepVerifier.create(voidMono)
.expectComplete().verify();
StepVerifier.create(mockResponse.getBody())
.consumeNextWith(a -> System.out.println(a))
.expectComplete().verify();
assertThat(mockResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
}
}
Error Message:
java.lang.AssertionError: expectation "expectComplete" failed (expected: onComplete(); actual: onError(org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'application/octet-stream' not supported for bodyType=com.example.demo.Person"))
I found that I missed .contentType(MediaType.APPLICATION_JSON) to my mock request.
MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create").contentType(MediaType.APPLICATION_JSON)
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
fixed my issue.

Unit testing of Saga handlers in rebus and correlation issues

I have this simple Saga in Rebus:
public void MySaga : Saga<MySagaData>
IAmInitiatedBy<Event1>
IHandleMessages<Event2>
{
private IBus bus;
private ILog logger;
public MySaga(IBus bus, ILog logger)
{
if (bus == null) throw new ArgumentNullException("bus");
if (logger == null) throw new ArgumentNullException("logger");
this.bus = bus;
this.logger = logger;
}
protected override void CorrelateMessages(ICorrelationConfig<MySagaData> config)
{
config.Correlate<Event>(m => m.MyObjectId.Id, s => s.Id);
config.Correlate<Event>(m => m.MyObjectId.Id, s => s.Id);
}
public Task Handle(Event1 message)
{
return Task.Run(() =>
{
this.Data.Id = message.MyObjectId.Id;
this.Data.State = MyEnumSagaData.Step1;
var cmd = new ResponseCommandToEvent1(message.MyObjectId);
bus.Send(cmd);
});
}
public Task Handle(Event2 message)
{
return Task.Run(() =>
{
this.Data.State = MyEnumSagaData.Step2;
var cmd = new ResponseCommandToEvent2(message.MyObjectId);
bus.Send(cmd);
});
}
}
and thanks to the kind mookid8000 I can test the saga using FakeBus and a SagaFixture:
[TestInitialize]
public void TestInitialize()
{
var log = new Mock<ILog>();
bus = new FakeBus();
fixture = SagaFixture.For<MySaga>(() => new MySaga(bus, log.Object));
idTest = new MyObjectId(Guid.Parse("1B2E7286-97E5-4978-B5B0-D288D71AD670"));
}
[TestMethod]
public void TestIAmInitiatedBy()
{
evt = new Event1(idTest);
fixture.Deliver(evt);
var testableFixture = fixture.Data.OfType<MySagaData>().First();
Assert.AreEqual(MyEnumSagaData.Step1, testableFixture.State);
// ... more asserts
}
[TestMethod]
public void TestIHandleMessages()
{
evt = new Event2(idTest);
fixture.Deliver(evt);
var testableFixture = fixture.Data.OfType<MySagaData>().First();
Assert.AreEqual(MyEnumSagaData.Step2, testableFixture.State);
// ... more asserts
}
[TestCleanup]
public void TestCleanup()
{
fixture.Dispose();
bus.Dispose();
}
The first test method that check IAmInitiatedBy is correctly executed and no error is thrown, while the second test fail. It looks like a correlation issues since fixture.Data contains no elements and in fixture.LogEvents contains as last elements this error: Could not find existing saga data for message Event2/b91d161b-eb1b-419d-9576-2c13cd9d9c51.
What is this GUID? Is completly different from the one I defined in the unit test? Any ideas? Is legal what I'm tryng to test (since I'm using an in-memory bus)?
This line is bad: this.Data.Id = message.MyObjectId.Id. If you checked the value of Data.Id before you overwrote it, you would have noticed that the property already had a value.
You do not assign the saga ID - Rebus does that. And you should leave that property alone :)
Regarding your error - when Rebus wants to log information about a specific message, it logs a short name for the type and the message ID, i.e. the value of the automatically-assigned rbs2-msg-id header. In other words: It's not the value of the property m.MyObjectId.Id, you're seeing, it's the message ID.
Since the saga fixture is re-initialized for every test run, and you only deliver an Event2 to it (which is not allowed to initiate a new instance), the saga will not be hit.

RhinoMocks and Lambda Expression

With the interface below:
public interface IRepository<T>
{
T FirstOrDefault(Func<T, bool> predicate);
}
Implemented like so:
private IRepository<Foo> Repository {get;set;}
public FooService(IRepository<Foo> repository)
{
Repository = repository;
}
public void Foo(int bar)
{
var result = Repository.FirstOrDefault(x => x.Id.Equals(bar));
if (result == null)
{
throw new Exception("Returned null, not what I expected!");
}
}
If I write a test like this:
repos = MockRepository.GenerateMock<IRepository<Foo>>(null);
repos.Expect(x => x.FirstOrDefault(y => y.Id.Equals(Arg<int>.Is.Anything))).Throw(new Exception("This exception should be thrown!"));
FooService f = new FooService(repos);
f.Foo(1);
//expecting exception to be thrown
I do not get the expected exception thrown, so I am presuming that mocked call is being ignored/not called.
However if I add IgnoreArguments() it is called and I do get the expected exception.
repos.Expect(x => x.FirstOrDefault(y => y.Id.Equals(Arg<int>.Is.Anything))).IgnoreArguments().Throw(new Exception("This exception should be thrown!"));
Any thoughts as to what it is I am doing wrong?

Unit testing an ActionFilter - correctly setting up the ActionExecutingContext

In a custom ActionFilter, I want check the attributes on the controller action that will be executed. Running through a small test application, the following works when launching the app in the asp.net development server-
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var someAttribute = filterContext.ActionDescriptor
.GetCustomAttributes(typeof(SomeAttribute), false)
.Cast<SomeAttribute>()
.SingleOrDefault();
if (someAttribute == null)
{
throw new ArgumentException();
}
// do something here
}
public override void OnActionExecuted(ActionExecutingContext filterContext)
{
// ...
}
}
An action method without SomeAttribute throws an ArgumentException and conversely, an action method with SomeAttribute does not. So far so good.
Now I would like to set up some unit tests for the ActionFilter, but how can I set up the action method upon which the OnActionExecuting method should run in the unit test? Using the following code doesn't find SomeAttribute on the action method which will be executed. Is the test set up correctly? Have I not arranged something correctly in the test? To clarify, the test is not complete but I'm not sure what I've missed such that someAttribute in OnActionExecuting in the test is null
[TestMethod]
public void Controller_With_SomeAttribute()
{
FakeController fakeController =
new FakeController();
ControllerContext controllerContext =
new ControllerContext(new Mock<HttpContextBase>().Object,
new RouteData(),
fakeController);
var actionDescriptor = new Mock<ActionDescriptor>();
actionDescriptor.SetupGet(x => x.ActionName).Returns("Action_With_SomeAttribute");
ActionExecutingContext actionExecutingContext =
new ActionExecutingContext(controllerContext,
actionDescriptor.Object,
new RouteValueDictionary());
CustomActionFilterAttribute customActionFilterAttribute = new CustomActionFilterAttribute ();
customActionFilterAttribute.OnActionExecuting(actionExecutingContext);
}
private class FakeController : Controller
{
[SomeAttribute]
ActionResult Action_With_SomeAttribute()
{
return View();
}
}
Since the ActionDescriptor property of ActionExecutingContext is virtual, you can just override that and provide your own implementation of ActionDescriptor.
Here are two tests that verify the two branches through the current implementation of OnActionExecuting:
[ExpectedException(typeof(ArgumentException))]
[TestMethod]
public void OnActionExecutingWillThrowWhenSomeAttributeIsNotPresent()
{
// Fixture setup
var ctxStub = new Mock<ActionExecutingContext>();
ctxStub.Setup(ctx => ctx.ActionDescriptor.GetCustomAttributes(typeof(SomeAttribute), false))
.Returns(new object[0]);
var sut = new CustomActionFilterAttribute();
// Exercise system
sut.OnActionExecuting(ctxStub.Object);
// Verify outcome (expected exception)
// Teardown
}
[TestMethod]
public void OnActionExecutingWillNotThrowWhenSomeAttributeIsPresent()
{
// Fixture setup
var ctxStub = new Mock<ActionExecutingContext>();
ctxStub.Setup(ctx => ctx.ActionDescriptor.GetCustomAttributes(typeof(SomeAttribute), false))
.Returns(new object[] { new SomeAttribute() });
var sut = new CustomActionFilterAttribute();
// Exercise system
sut.OnActionExecuting(ctxStub.Object);
// Verify outcome (no exception indicates success)
// Teardown
}