-
-
Notifications
You must be signed in to change notification settings - Fork 802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using object parameter in callback function in place of It.IsAnyType throws exception #1137
Comments
I suspect the problem lies not with the third parameter ( I may have added support for nested type matchers in #1092, but I was focusing on ⇒ Since you're currently matching the fifth parameter using P.S.: IMHO the signature of that partial class LoggerMock<T> : ILogger<T>
{
private readonly Action<LogLevel, object> log;
public Logger(Func<LogLevel, object> log) => this.log = log;
public void Log<TState>(LogLevel level, EventId _, TState state, Exception __, Func<TState, Exception, string> ___) => this.log(level, state);
}
...
var loggerMock = new LoggerMock<Foobar>((level, obj) => messages.Add(Tuple.Create(level, obj.ToString())); |
Using Regarding the question why this signature of
Since I got a workaround now and this feature was apparently not planned to work with nested type matcher in Callbacks in Moq 4, I'm fine with closing this issue. |
I'm mostly surprised that people need to mock I can take a look if it's possible at all to make callback parameter validation less strict in this situation, but I'm not yet sure this is always possible in principle... so I may close this issue soon without taking any further action. |
Well, everything is injected via constructor, so its mandatory by default. Also, the issue lies when one tries to use the Strict mode with auto mock Frameworks which will create a mock for every injected parameter of the system under test. In Strict mode, if you don't have a Setup, it will fail. But also there are cases where we want to make sure the logger is called, since logs are important for debugging so some information we want to make sure are there (Exceptions, Errors, Warnings) |
Thanks for replying to my question. Makes perfect sense.
But it's not mandatory to inject a mock logger, right? You could easily implement a do-nothing stub by hand and inject that. (But sure, creating a throwaway mock object may end up being less work.)
Granted, I suppose that makes sense. I wasn't aware that people unit-test their logging, I guess I learnt something new today. :-) |
I'm closing this issue, since the immediate problem has been resolved. I've opened a new issue that addresses the one remaining task regarding Moq's callback validation. |
This doesn't compile. Is there another example? |
You can't use class Client
{
void Foo(Func<T, Func<int, bool>> callback)
{
callback(IsZero);
}
bool IsZero(int value) => value == 0;
// the client knows how to compare 0 with int, but has no idea how to compare 0 with any other type,
// so if we suddenly wanted Func<int, object>>, he would be confused
} If you're interested, this comes down to the notion of type covariance and contravariance in C#. The way out in a particular case is this: generalize not the generic type, but the argument itself: that is, ask the client for SOME delegates, and decide for yourself what type it should be: Action<LogLevel, EventId, object, Exception?, Delegate> invokeCallback = (l, _, s, ex, f) =>
{
var message = f.DynamicInvoke(s, ex) as string;
_messages.Add(message);
};
mock.Setup(s => s.Log(It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
.Callback(invokeCallback); |
I am struggling with something similar, but with more generics :) I have a simple interface: public interface IQuery {}
public interface IQuery<TResult> : IQuery { }
public interface IQueryDispatcher
{
Task<Result<TResult>> ExecuteAsync<TQuery, TResult>(
TQuery query,
CancellationToken cancellationToken)
where TQuery : IQuery<TResult>;
} An example call of this: var response = await QueryDispatcher
.ExecuteAsync<ListNewsletterSubscriptionsQuery, IEnumerable<ListNewsletterSubscriptionsResponse>>(
new ListNewsletterSubscriptionsQuery(emailClaim.Value), ct); But I can't figure out how to define the Setup to catch all possible invocations. I've tried this, but it never gets hit: queryDispatcher
.Setup(x => x.ExecuteAsync<IQuery<It.IsAnyType>, It.IsAnyType>(
It.IsAny<IQuery<It.IsAnyType>>(),
It.IsAny<CancellationToken>()))
.Callback((IQuery query, CancellationToken _) => queryCapture((TQuery)query)) I can't pass I've also tried to be adventerous with queryDispatcher
.Setup(x => x.ExecuteAsync<IQuery<dynamic>, dynamic>(
It.IsAny<IQuery<dynamic>>(),
It.IsAny<CancellationToken>()))
.Callback((dynamic query, CancellationToken _) => queryCapture((TQuery)query)) Is there a way to catch this kind of structure? |
For anyone stumbling on this and to answer @lorddev here's a version of the LoggerMock that compiles. I added the Exception because I needed to validate that exceptions were indeed being logged (we use Seq a lot for troubleshooting problems). Many thanks to @stakx for the suggestion.
My usage:
|
I needed to mock an I tried mocking the ILogger interface but couldn't, so I created a custom logger class instead. Here's the code: private sealed class TestOutputHelperLogger : ILogger, IDisposable
{
private readonly string _name;
private readonly ITestOutputHelper _testOutputHelper;
private static readonly ConsoleFormatter Formatter = CreateSimpleConsoleFormatter();
private static readonly IExternalScopeProvider ExternalScopeProvider =
new Mock<IExternalScopeProvider>().Object;
public TestOutputHelperLogger(string name, ITestOutputHelper testOutputHelper)
{
_name = name;
_testOutputHelper = testOutputHelper;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
var logEntry = new LogEntry<TState>(logLevel, _name, eventId, state, exception, formatter);
var stringWriter = new StringWriter();
Formatter.Write(logEntry, ExternalScopeProvider, stringWriter);
_testOutputHelper.WriteLine(stringWriter.ToString());
}
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
return this;
}
public void Dispose()
{
}
private static ConsoleFormatter CreateSimpleConsoleFormatter()
{
const string simpleConsoleFormatterTypeName = "SimpleConsoleFormatter";
const string formatterOptionsMonitorTypeName = "FormatterOptionsMonitor";
var formatterAssemblyTypes = typeof(ConsoleFormatter).Assembly.GetTypes();
Type simpleConsoleFormatterType = formatterAssemblyTypes
.FirstOrDefault(t => t.Name == simpleConsoleFormatterTypeName);
Type formatterOptionsMonitorType = formatterAssemblyTypes
.FirstOrDefault(t => t.Name.StartsWith(formatterOptionsMonitorTypeName));
if (simpleConsoleFormatterType == null)
throw new Exception(
"Internal class SimpleConsoleFormatter was not found. Probably, its name changed after the migration to a newer .NET version");
if (formatterOptionsMonitorType == null)
throw new Exception(
"Internal class FormatterOptionsMonitor<T> was not found. Probably, its name changed after the migration to a newer .NET version");
var monitorOfSimpleConsoleFormatterType =
formatterOptionsMonitorType.MakeGenericType(typeof(SimpleConsoleFormatterOptions));
var monitorOfSimpleConsoleFormatter = Activator.CreateInstance(monitorOfSimpleConsoleFormatterType,
new SimpleConsoleFormatterOptions());
return (ConsoleFormatter)Activator.CreateInstance(simpleConsoleFormatterType,
monitorOfSimpleConsoleFormatter);
}
} This logger uses existing SimpleConsoleFormatter, which prepares the output in console-based format. Mock<ILoggerFactory> loggerFactoryMock = new Mock<ILoggerFactory>();
loggerFactoryMock.Setup(f => f.CreateLogger(It.IsAny<string>()))
.Returns((string name) => new TestOutputHelperLogger(name, _testOutputHelper));
services.AddSingleton(loggerFactoryMock.Object);
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); |
In my .Net Core 3.1 app (using Moq 4.16.0) I want to configure a callback on a mocked ILogger object during TestInit(), however, it fails with the following exception when I run the test:
The code for my minimal working example
and the test project looks like
According to this comment it should be possible to use object where It.IsAnyType is used. This is confirmed in the corresponding issue #953 (comment). However, I get the exception and even after reading the Quickguide I don't understand what I'm doing wrong. I guess the issue is with the Func<>? I'd appreciate if someone could provide a working example for such a use case.
The text was updated successfully, but these errors were encountered: