0

I am trying to understand why I am unable to AutoMock.Mock an interface, and whether the presence of non-interfaces (like string key) or delegates (like Foo.Factory) are part of the problem and what to do about them.

I know about RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource())m but that seems useless w/o a way to actually pass in the non-interface types and the values I want to use for each of them atm. Sort of like a mock.Setup can do per parameter, but here done at top-level only where it is like saying "every time you see T as you descend through the mocking, use value V for it". But I don't know what support is there for doing that. For testing, I'd think it is pretty common to need to resolve a set of mocks, with fill-in-the-blank values for non-registered or non-interface params it encounters.

In fact, the non-interface T values I wish I could register for AutoMock.Mock are almost always the same args as to the delegate factory args (like in this example below, string key alone).

Here is a short example of trying to use the delegate factory in AutoMock.

A couple of questions:

  1. Why does the lack of Register.AsSelf() cause foo3.Key to be "test2" instead of "test3"?
  2. How do you get an AutoMock.Mock to generate? I tried no params (which I can understand not working since the string key at least isn't registered). I also tried TypedParameters for all 3 possible args (for either the normal Foo ctor or the delegate factory since I don't know which it tries).

Here is an XUnit example with a few combinations of container registration I tried.

Hopefully someone can explain the subtleties of delegate factory usage and pitfalls, as I am using them in a more nested way than this simple example, and I would like to especially understand what I am doing wrong wrt getting a mock from the different interface levels.

using Autofac;
using Autofac.Core;
using Autofac.Extras.Moq;
using Divergic.Logging.Xunit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using System;
using Xunit;
using Xunit.Abstractions;

namespace MyTesting
{
    public class SimpleTests
    {
        public interface IFooProvider
        {
            IFoo GetFoo(string key);
        }

        public interface IFoo
        {
            string Key { get; }
        }

        public class FooProvider : IFooProvider
        {
            ILogger<FooProvider> Logger { get; init; }
            IConfiguration Config { get; init; }
            Foo.Factory Factory { get; init; }

            public FooProvider(ILogger<FooProvider> logger, IConfiguration config, Foo.Factory factory)
            {
                Logger = logger;
                Config = config;
                Factory = factory;
            }

            public IFoo GetFoo(string key) => Factory(key);
        }

        public class Foo : IFoo
        {
            ILogger Logger { get; init; }
            IConfiguration Config { get; init; }
            public string Key { get; init; }

            public delegate Foo Factory(string key);

            public Foo(ILogger logger, IConfiguration config, string key)
            {
                Logger = logger;
                Config = config;
                Key = key;
            }
        }

        private ILogger Log { get; init; }

        public SimpleTests(ITestOutputHelper output)
        {
            Log = LogFactory.Create(output).CreateLogger(GetType().Name);
        }

        [Fact]
        public void TestMe()
        {
            Action<int, bool, bool> action = (pass, registerFooAsSelf, registerFooFactory) =>
            {
                Log.LogInformation($"\n\n======== Pass {pass}: {(registerFooAsSelf?"Register<Foo>.AsSelf()":"")} {(registerFooFactory?"Register<Foo.Factory>(c => ...)":"")} ========");

                AutoMock Mock = AutoMock.GetStrict(cfg =>
                {
                    cfg.RegisterType<FooProvider>().SingleInstance().AsSelf();
                    cfg.RegisterType<Foo>().As<IFoo>();
                    if (registerFooAsSelf)
                        // Necessary for delegate factory? otherwise foo3 ends up as "test2" instead of "test3"
                        cfg.RegisterType<Foo>().AsSelf();
                    if (registerFooFactory)
                        // I assume this is equivalent to the auto-generated impl and can be omitted unless I had custom logic
                        cfg.Register<Foo.Factory>(c =>
                        {
                            var loggerFactory = c.Resolve<ILoggerFactory>();
                            var config = c.Resolve<IConfiguration>();
                            var foo = c.Resolve<Func<ILoggerFactory, IConfiguration, string, Foo>>();
                            return (string key) => foo(loggerFactory, config, key);
                        });
                });

                var logger = Mock.Mock<ILogger>();
                var config = Mock.Mock<IConfiguration>();
                var p1 = Mock.Mock<IFooProvider>();
                p1.Setup(p => p.GetFoo(It.IsAny<string>())).Returns((string s) => new Foo(logger.Object, config.Object, s));
                var foo1 = p1.Object.GetFoo("test1");
                Log.LogInformation($"foo1={foo1.Key}");

                var p2 = Mock.Create<FooProvider>();
                var foo2 = p2.GetFoo("test2");
                Log.LogInformation($"foo2={foo2.Key}");

                var foo3 = Mock.Create<Foo.Factory>()("test3");
                Log.LogInformation($"foo3={foo3.Key} {(!"test3".Equals(foo3.Key)?" ** != **":"")}");

                // But how do I generate a mock of IFoo?

                // Autofac.Core.DependencyResolutionException : An exception was thrown while activating Microsoft.CCI.Common.Tests.Authentication.SimpleTests + Foo.
                //---- Autofac.Core.DependencyResolutionException : None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'
                // on type 'Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo' can be invoked with the available services and parameters:
                // Cannot resolve parameter 'System.String key' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Configuration.IConfiguration, System.String)'.
                LogThrow<DependencyResolutionException>(() =>
                {
                    var foo4 = Mock.Mock<IFoo>();
                }, " foo4: Error expected, since string key is not registered");

                // System.InvalidCastException : Unable to cast object of type 'Foo' to type 'Moq.IMocked`1[Microsoft.CCI.Common.Tests.Authentication.SimpleTests+IFoo]'.
                LogThrow<InvalidCastException>(() =>
                {
                    var mockFoo5 = Mock.Mock<IFoo>(
                    new TypedParameter(typeof(ILogger), logger.Object),
                    new TypedParameter(typeof(IConfiguration), config.Object),
                    new TypedParameter(typeof(string), "test5"));
                }, " foo5: Error not expected, all ctor or delegate Factory params are given, so why doesn't this work?");
            };

            action(1, false, false);
            action(2, false, true);
            action(3, true, false);
            action(4, true, true);
        }

        private void LogThrow<T>(Action action, string msg) where T : Exception
        {
            Assert.Throws<T>(() =>
            {
                try
                {
                    action();
                }
                catch (T ex)
                {
                    //Log.LogError(ex, msg);
                    Log.LogError($"{ex.GetType().Name}: {ex.Message}\n" +
                        $"{(ex.InnerException != null ? $"{ex.InnerException.GetType().Name}: {ex.InnerException.Message}\n" : "")}{msg}");
                    throw;
                }
            });
        }
    }
}

Output:

======== Pass 1:   ========
Information [0]: foo1=test1
Information [0]: foo2=test2
Information [0]: foo3=test2  ** != **
Error [0]: DependencyResolutionException: An exception was thrown while activating Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String key' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Configuration.IConfiguration, System.String)'.
 foo4: Error expected, since string key is not registered
Error [0]: InvalidCastException: Unable to cast object of type 'Foo' to type 'Moq.IMocked`1[Microsoft.CCI.Common.Tests.Authentication.SimpleTests+IFoo]'.
 foo5: Error not expected, all ctor or delegate Factory params are given, so why doesn't this work?

======== Pass 2:  Register<Foo.Factory>(c => ...) ========
Information [0]: foo1=test1
Information [0]: foo2=test2
Information [0]: foo3=test2  ** != **
Error [0]: DependencyResolutionException: An exception was thrown while activating Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String key' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Configuration.IConfiguration, System.String)'.
 foo4: Error expected, since string key is not registered
Error [0]: InvalidCastException: Unable to cast object of type 'Foo' to type 'Moq.IMocked`1[Microsoft.CCI.Common.Tests.Authentication.SimpleTests+IFoo]'.
 foo5: Error not expected, all ctor or delegate Factory params are given, so why doesn't this work?

======== Pass 3: Register<Foo>.AsSelf()  ========
Information [0]: foo1=test1
Information [0]: foo2=test2
Information [0]: foo3=test3 
Error [0]: DependencyResolutionException: An exception was thrown while activating Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String key' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Configuration.IConfiguration, System.String)'.
 foo4: Error expected, since string key is not registered
Error [0]: InvalidCastException: Unable to cast object of type 'Foo' to type 'Moq.IMocked`1[Microsoft.CCI.Common.Tests.Authentication.SimpleTests+IFoo]'.
 foo5: Error not expected, all ctor or delegate Factory params are given, so why doesn't this work?

======== Pass 4: Register<Foo>.AsSelf() Register<Foo.Factory>(c => ...) ========
Information [0]: foo1=test1
Information [0]: foo2=test2
Information [0]: foo3=test3 
Error [0]: DependencyResolutionException: An exception was thrown while activating Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Microsoft.CCI.Common.Tests.Authentication.SimpleTests+Foo' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String key' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Configuration.IConfiguration, System.String)'.
 foo4: Error expected, since string key is not registered
Error [0]: InvalidCastException: Unable to cast object of type 'Foo' to type 'Moq.IMocked`1[Microsoft.CCI.Common.Tests.Authentication.SimpleTests+IFoo]'.
 foo5: Error not expected, all ctor or delegate Factory params are given, so why doesn't this work?

redgiant
  • 474
  • 5
  • 14

0 Answers0