diff --git a/FastMoq.Core/Mocker.cs b/FastMoq.Core/Mocker.cs index 22e054e..e1d5a6d 100644 --- a/FastMoq.Core/Mocker.cs +++ b/FastMoq.Core/Mocker.cs @@ -3,8 +3,10 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Moq; +using Moq.Language.Flow; using Moq.Protected; using System.Collections; +using System.Collections.ObjectModel; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; @@ -32,21 +34,26 @@ public class Mocker public readonly MockFileSystem fileSystem; /// - /// List of . + /// The list of types in the process of being created. This is used to prevent circular creations. /// - protected internal readonly List mockCollection; + protected internal readonly List creatingTypeList = new(); /// - /// The list of types in the process of being created. This is used to prevent circular creations. + /// List of . /// - protected internal readonly List creatingTypeList = new List(); + protected internal readonly List mockCollection; /// /// of mapped to . - /// This map assists in resolution of interfaces to instances. + /// This map assists in resolution of interfaces to instances. /// /// The type map. - internal readonly Dictionary typeMap; + internal readonly Dictionary typeMap; + + /// + /// The constructor history + /// + private readonly Dictionary> constructorHistory = new(); /// /// The setup HTTP factory @@ -57,6 +64,13 @@ public class Mocker #region Properties + /// + /// Gets the constructor history. + /// + /// The constructor history. + public ILookup> ConstructorHistory => + constructorHistory.ToLookup(pair => pair.Key, pair => pair.Value.AsReadOnly()); + /// /// Gets the database connection. /// @@ -78,14 +92,18 @@ public class Mocker /// /// Gets or sets a value indicating whether this is strict. /// - /// true if strict resolution; otherwise, false uses the built-in virtual - /// . - /// If strict, the mock - /// does - /// not use and uses of . - /// Gets or sets a value indicating whether this is strict. If strict, the mock - /// does - /// not use the pre-built HttpClient and uses of . + /// + /// true if strict resolution; otherwise, false uses the built-in virtual + /// . + /// + /// + /// If strict, the mock + /// does + /// not use and uses of . + /// Gets or sets a value indicating whether this is strict. If strict, the mock + /// does + /// not use the pre-built HttpClient and uses of . + /// public bool Strict { get; set; } #endregion @@ -107,7 +125,7 @@ public Mocker() /// The typeMap assists in resolution of interfaces to instances. /// /// The type map. - public Mocker(Dictionary typeMap) : this() => this.typeMap = typeMap; + public Mocker(Dictionary typeMap) : this() => this.typeMap = typeMap; /// /// Adds the injections to the specified object properties and fields. @@ -135,17 +153,70 @@ public T AddInjections(T obj, Type? referenceType = null) where T : class? /// /// Creates a with the given with the option of overwriting an existing - /// + /// /// /// The Mock , usually an interface. /// Mock to Add. - /// Overwrite if the mock exists or throw if this parameter is - /// false. + /// + /// Overwrite if the mock exists or throw if this parameter is + /// false. + /// /// if set to true uses public and non public constructors. /// . public MockModel AddMock(Mock mock, bool overwrite, bool nonPublic = false) where T : class => new(AddMock(mock, typeof(T), overwrite, nonPublic)); + /// + /// Adds the property data to the object. + /// + /// + /// The object. + /// T. + public T? AddProperties(T obj) + { + var o = AddProperties(typeof(T), obj); + return o is not null ? (T) o : default; + } + + /// + /// Adds the property data to the object. + /// + /// The type. + /// The object. + /// object. + public object? AddProperties(Type type, object? obj) + { + if (creatingTypeList.Contains(type)) + { + return obj; + } + + try + { + creatingTypeList.Add(type); + var writableProperties = type.GetProperties().Where(x => x.CanWrite && x.CanRead).ToList(); + foreach (var writableProperty in writableProperties) + { + try + { + if (writableProperty.GetValue(obj) is null && !creatingTypeList.Contains(writableProperty.PropertyType)) + { + writableProperty.SetValue(obj, GetObject(writableProperty.PropertyType)); + } + } + catch + { + // Continue + } + } + } + finally + { + creatingTypeList.Remove(type); + } + + return obj; + } /// /// Adds an interface to Class mapping to the for easier resolution. @@ -154,9 +225,10 @@ public MockModel AddMock(Mock mock, bool overwrite, bool nonPublic = fa /// The Class Type (cannot be an interface) that can be created and assigned to tInterface. /// An optional create function used to create the class. /// Replace type if already exists. Default: false. + /// arguments needed in model. /// $"{tClass.Name} cannot be an interface. /// $"{tClass.Name} is not assignable to {tInterface.Name}. - public void AddType(Type tInterface, Type tClass, Func? createFunc = null, bool replace = false) + public void AddType(Type tInterface, Type tClass, Func? createFunc = null, bool replace = false, params object?[]? args) { if (tClass.IsInterface) { @@ -173,7 +245,7 @@ public void AddType(Type tInterface, Type tClass, Func? createFu typeMap.Remove(tInterface); } - typeMap.Add(tInterface, new InstanceModel(tClass, createFunc)); + typeMap.Add(tInterface, new InstanceModel(tInterface, tClass, createFunc, args?.ToList() ?? new())); } /// @@ -182,7 +254,9 @@ public void AddType(Type tInterface, Type tClass, Func? createFu /// /// The create function. /// if set to true [replace]. - public void AddType(Func? createFunc = null, bool replace = false) where T : class => AddType(createFunc, replace); + /// arguments needed in model. + public void AddType(Func? createFunc = null, bool replace = false, params object?[]? args) where T : class => + AddType(createFunc, replace, args); /// /// Adds an interface to Class mapping to the for easier resolution. @@ -191,10 +265,11 @@ public void AddType(Type tInterface, Type tClass, Func? createFu /// The Class Type (cannot be an interface) that can be created and assigned to TInterface />. /// An optional create function used to create the class. /// Replace type if already exists. Default: false. + /// arguments needed in model. /// $"{typeof(TClass).Name} cannot be an interface." /// $"{typeof(TClass).Name} is not assignable to {typeof(TInterface).Name}." - public void AddType(Func? createFunc = null, bool replace = false) - where TInterface : class where TClass : class => AddType(typeof(TInterface), typeof(TClass), createFunc, replace); + public void AddType(Func? createFunc = null, bool replace = false, params object?[]? args) + where TInterface : class where TClass : class => AddType(typeof(TInterface), typeof(TClass), createFunc, replace, args); /// /// Determines whether this instance contains a Mock of T. @@ -212,10 +287,14 @@ public void AddType(Func? createFunc = null, /// true if exists; otherwise, false. /// type /// type must be a class. - type - public bool Contains(Type type) => - type == null ? throw new ArgumentNullException(nameof(type)) : - !type.IsClass && !type.IsInterface ? throw new ArgumentException("type must be a class.", nameof(type)) : - mockCollection.Any(x => x.Type == type); + public bool Contains(Type type) + { + ArgumentNullException.ThrowIfNull(type); + + return !type.IsClass && !type.IsInterface + ? throw new ArgumentException("type must be a class.", nameof(type)) + : mockCollection.Any(x => x.Type == type); + } /// /// Creates the HTTP client. @@ -233,10 +312,10 @@ public HttpClient CreateHttpClient(string clientName = "FastMoqHttpClient", stri if (!Contains()) { SetupHttpMessage(() => new HttpResponseMessage - { - StatusCode = statusCode, - Content = new StringContent(stringContent) - } + { + StatusCode = statusCode, + Content = new StringContent(stringContent), + } ); } @@ -253,13 +332,13 @@ public HttpClient CreateHttpClient(string clientName = "FastMoqHttpClient", stri /// /// Creates an instance of T. Parameters allow matching of constructors and using those values in the creation - /// of the instance. + /// of the instance. /// /// The Mock , usually an interface. /// The optional arguments used to create the instance. /// . /// - /// (); /// ]]> /// @@ -459,11 +538,24 @@ public HttpClient CreateHttpClient(string clientName = "FastMoqHttpClient", stri } var tType = typeof(T); - var typeInstanceModel = GetMapModel() ?? (tType.IsInterface ? GetTypeFromInterface() : new InstanceModel()); + var typeInstanceModel = GetTypeModel(); - if (typeInstanceModel.CreateFunc != null) + if (typeInstanceModel.CreateFunc != null && !creatingTypeList.Contains(tType)) { - return (T)typeInstanceModel.CreateFunc.Invoke(this); + creatingTypeList.Add(tType); + T obj; + + try + { + AddToConstructorHistory(tType, typeInstanceModel); + obj = (T) typeInstanceModel.CreateFunc.Invoke(this); + } + finally + { + creatingTypeList.Remove(tType); + } + + return obj; } args ??= Array.Empty(); @@ -478,14 +570,16 @@ public HttpClient CreateHttpClient(string clientName = "FastMoqHttpClient", stri /// /// Creates an instance of T. - /// Non public constructors are included as options for creating the instance. - /// Parameters allow matching of constructors and using those values in the creation of the instance. + /// Non public constructors are included as options for creating the instance. + /// Parameters allow matching of constructors and using those values in the creation of the instance. /// /// The Mock , usually an interface. /// The arguments. - /// + /// + /// + /// /// - /// (); /// ]]> /// @@ -494,7 +588,7 @@ public HttpClient CreateHttpClient(string clientName = "FastMoqHttpClient", stri var type = typeof(T).IsInterface ? GetTypeFromInterface() : new InstanceModel(); return type.CreateFunc != null - ? (T)type.CreateFunc.Invoke(this) + ? (T) type.CreateFunc.Invoke(this) : CreateInstanceNonPublic(type.InstanceType, args) as T; } @@ -568,7 +662,6 @@ public Mock CreateMockInstance(Type type, bool nonPublic = false, params object? throw new ArgumentException("type must be a class or interface.", nameof(type)); } - var constructor = new ConstructorModel(null, args.ToList()); try @@ -596,80 +689,11 @@ public Mock CreateMockInstance(Type type, bool nonPublic = false, params object? } } - AddInjections(oMock.Object, GetMapModel(type)?.InstanceType ?? type); + AddInjections(oMock.Object, GetTypeModel(type)?.InstanceType ?? type); return oMock; } - /// - /// Creates the mock internal. - /// - /// The type to create. - /// The constructor model. - /// Mock. - private Mock CreateMockInternal(Type type, ConstructorModel constructor) - { - var newType = typeof(Mock<>).MakeGenericType(type); - - // Execute new Mock with Loose Behavior and arguments from constructor, if applicable. - var parameters = new List { Strict ? MockBehavior.Strict : MockBehavior.Loose }; - constructor?.ParameterList.ToList().ForEach(parameters.Add); - - return Activator.CreateInstance(newType, parameters.ToArray()) is not Mock oMock ? throw new ApplicationException("Cannot create instance.") : oMock; - } - - /// - /// Adds the property data to the object. - /// - /// - /// The object. - /// T. - public T? AddProperties(T obj) - { - var o = AddProperties(typeof(T), obj); - return o is not null ? (T)o : default; - } - - /// - /// Adds the property data to the object. - /// - /// The type. - /// The object. - /// object. - public object? AddProperties(Type type, object? obj) - { - if (creatingTypeList.Contains(type)) - { - return obj; - } - - try - { - creatingTypeList.Add(type); - var writableProperties = type.GetProperties().Where(x => x.CanWrite && x.CanRead).ToList(); - foreach (var writableProperty in writableProperties) - { - try - { - if (writableProperty.GetValue(obj) is null && !creatingTypeList.Contains(writableProperty.PropertyType)) - { - writableProperty.SetValue(obj, GetObject(writableProperty.PropertyType)); - } - } - catch - { - // Continue - } - } - } - finally - { - creatingTypeList.Remove(type); - } - - return obj; - } - /// /// Creates the mock instance that is not automatically injected. /// @@ -679,7 +703,8 @@ private Mock CreateMockInternal(Type type, ConstructorModel constructor) /// Mock. /// type must be a class. - type /// Cannot create instance. - public Mock CreateMockInstance(bool nonPublic = false, params object?[] args) where T : class => (Mock)CreateMockInstance(typeof(T), nonPublic, args); + public Mock CreateMockInstance(bool nonPublic = false, params object?[] args) where T : class => + (Mock) CreateMockInstance(typeof(T), nonPublic, args); /// /// Gets the argument data. @@ -712,6 +737,34 @@ public async Task GetContentBytes(HttpContent content) => public async Task GetContentStream(HttpContent content) => content is ByteArrayContent data ? await data.ReadAsStreamAsync() : Stream.Null; + /// + /// Gets the database context. + /// + /// The type of the t context. + /// TContext. + public TContext GetDbContext() where TContext : DbContext, new() => GetDbContext(_ => new TContext()); + + /// + /// Gets the database context. + /// + /// The type of the t context. + /// The new object function. + /// TContext. + public TContext GetDbContext(Func newObjectFunc) where TContext : DbContext + { + DbConnection = new SqliteConnection("DataSource=:memory:"); + DbConnection.Open(); + var dbContextOptions = new DbContextOptionsBuilder() + .UseSqlite(DbConnection) + .Options; + + var context = newObjectFunc(dbContextOptions); + context.Database.EnsureCreated(); + context.SaveChanges(); + + return context; + } + /// /// Gets the default value. /// @@ -723,9 +776,20 @@ public async Task GetContentStream(HttpContent content) => { FullName: "System.String" } => string.Empty, _ when typeof(IEnumerable).IsAssignableFrom(type) => Array.CreateInstance(type.GetElementType() ?? typeof(object), 0), { IsClass: true } => null, - _ => Activator.CreateInstance(type) + _ => Activator.CreateInstance(type), }; + /// + /// Gets the HTTP handler setup. + /// + /// The request. + /// The cancellation token. + /// ISetup<HttpMessageHandler, Task<HttpResponseMessage>>. + public ISetup>? GetHttpHandlerSetup(Expression? request = null, + Expression? cancellationToken = null) => + GetMessageProtectedAsync("SendAsync", request ?? ItExpr.IsAny(), + cancellationToken ?? ItExpr.IsAny()); + /// /// Gets a list with the specified number of list items, using a custom function. /// @@ -735,14 +799,15 @@ _ when typeof(IEnumerable).IsAssignableFrom(type) => Array.CreateInstance(type.G /// The initialize action. /// . /// - /// Example of how to create a list. - /// (3, (i) => new Model(name: i.ToString())); /// ]]> - /// or - /// (3, (i) => Mocks.CreateInstance(i)); - /// ]]> + /// ]]> + /// public static List GetList(int count, Func? func, Action? initAction) { var results = new List(); @@ -768,14 +833,15 @@ public static List GetList(int count, Func? func, Action? /// The function for creating the list items. /// . /// - /// Example of how to create a list. - /// (3, (i) => new Model(name: i.ToString())); /// ]]> - /// or - /// (3, (i) => Mocks.CreateInstance(i)); - /// ]]> + /// ]]> + /// public static List GetList(int count, Func? func) => GetList(count, func, null); /// @@ -786,17 +852,31 @@ public static List GetList(int count, Func? func, Action? /// The function for creating the list items. /// . /// - /// Example of how to create a list. - /// (3, () => new Model(name: Guid.NewGuid().ToString())); /// ]]> - /// or - /// (3, () => Mocks.CreateInstance()); - /// ]]> + /// ]]> + /// public static List GetList(int count, Func? func) => func == null ? new List() : GetList(count, _ => func.Invoke()); + /// + /// Gets the message protected asynchronous. + /// + /// The type of the t mock. + /// The type of the t return. + /// Name of the method or property. + /// The arguments. + /// ISetup<TMock, Task<TReturn>>. + public ISetup>? GetMessageProtectedAsync(string methodOrPropertyName, params object?[]? args) + where TMock : class => + GetMock().Protected() + ?.Setup>(methodOrPropertyName, args ?? Array.Empty()); + /// /// Gets the method argument data. /// @@ -850,7 +930,7 @@ public static List GetList(int count, Func? func) => /// The Mock , usually an interface. /// The arguments to get the constructor. /// . - public Mock GetMock(params object?[] args) where T : class => (Mock)GetMock(typeof(T), args); + public Mock GetMock(params object?[] args) where T : class => (Mock) GetMock(typeof(T), args); /// /// Gets of creates the mock of type. @@ -874,7 +954,9 @@ public Mock GetMock(Type type, params object?[] args) /// Gets the instance for the given . /// /// The . - /// + /// + /// + /// /// nameof(info) /// nameof(info) /// nameof(info) @@ -895,13 +977,6 @@ public Mock GetMock(Type type, params object?[] args) } } - /// - /// Ensure Type is correct. - /// - /// The type. - /// Type. - internal Type CleanType(Type type) => (type.Name.EndsWith('&')) ? type.Assembly.GetTypes().FirstOrDefault(x => x.Name.Equals(type.Name.TrimEnd('&'))) ?? type : type; - /// /// Gets the instance for the given type. /// @@ -920,12 +995,12 @@ public Mock GetMock(Type type, params object?[] args) type = CleanType(type); - var typeValueModel = GetMapModel(type); + var typeValueModel = GetTypeModel(type); - if (typeValueModel?.CreateFunc != null) + if (typeValueModel.CreateFunc != null) { // If a create function is provided, use it instead of a mock object. - return AddInjections(typeValueModel.CreateFunc?.Invoke(this), typeValueModel.InstanceType); + return AddInjections(typeValueModel.CreateFunc.Invoke(this), typeValueModel.InstanceType); } if (!Strict) @@ -1009,7 +1084,7 @@ public Mock GetRequiredMock(Type type) => type == null || (!type.IsClass && !typ /// . /// type must be a class. - type /// Mock must exist. - type - public Mock GetRequiredMock() where T : class => (Mock)GetRequiredMock(typeof(T)); + public Mock GetRequiredMock() where T : class => (Mock) GetRequiredMock(typeof(T)); /// /// Gets the content of the string. @@ -1024,17 +1099,22 @@ public async Task GetStringContent(HttpContent content) => /// /// The Mock , usually an interface. /// The action. - /// False to keep the existing setup. - /// + /// + /// False to keep the existing setup. + /// + /// + /// + /// /// Invalid Mock. /// - /// Example of how to set up for mocks that require specific functionality. - /// (mock => { /// mock.Setup(x => x.StartCar).Returns(true)); /// mock.Setup(x => x.StopCar).Returns(false)); /// } - /// ]]> + /// ]]> + /// public Mock Initialize(Action> action, bool reset = true) where T : class { var mock = GetMock() ?? throw new InvalidOperationException("Invalid Mock."); @@ -1082,9 +1162,12 @@ public Mock Initialize(Action> action, bool reset = true) where T var method = type.InstanceType.GetMethod(methodName, flags); - return method == null && !nonPublic && !Strict ? InvokeMethod(obj, methodName, true, args) : - method == null ? throw new ArgumentOutOfRangeException() : - method.Invoke(obj, flags, null, args?.Any() ?? false ? args.ToArray() : GetMethodArgData(method), null); + return method switch + { + null when !nonPublic && !Strict => InvokeMethod(obj, methodName, true, args), + null => throw new ArgumentOutOfRangeException(), + _ => method.Invoke(obj, flags, null, args?.Any() ?? false ? args.ToArray() : GetMethodArgData(method), null), + }; } /// @@ -1138,7 +1221,7 @@ public void SetupMessageAsync(Expression (GetMock() .Setup(expression) ?? throw new InvalidDataException($"Unable to setup '{typeof(TMock)}'.")) - .ReturnsAsync(messageFunc)?.Verifiable(); + .ReturnsAsync(messageFunc)?.Verifiable(); /// /// Setups the message protected. @@ -1165,16 +1248,18 @@ public void SetupMessageProtected(string methodOrPropertyName, F public void SetupMessageProtectedAsync(string methodOrPropertyName, Func messageFunc, params object?[]? args) where TMock : class => GetMock().Protected() - ?.Setup>(methodOrPropertyName, args ?? Array.Empty()) - ?.ReturnsAsync(messageFunc)?.Verifiable(); + ?.Setup>(methodOrPropertyName, args ?? Array.Empty()) + ?.ReturnsAsync(messageFunc)?.Verifiable(); /// /// Add specified Mock. Internal API only. /// /// Mock to Add. /// Type of Mock. - /// Overwrite if the mock exists or throw if this parameter is - /// false. + /// + /// Overwrite if the mock exists or throw if this parameter is + /// false. + /// /// if set to true [non public]. /// . /// nameof(mock) @@ -1210,6 +1295,51 @@ internal MockModel AddMock(Mock mock, Type type, bool overwrite = false, bool no return GetMockModel(type); } + /// + /// Adds to constructor history. + /// + /// The key. + /// The instance model. + /// bool. + internal bool AddToConstructorHistory(Type key, IHistoryModel instanceModel) + { + if (key is null || instanceModel is null) + { + return false; + } + + var item = ConstructorHistory.FirstOrDefault(x => x.Key == key); + if (item?.Key is null) + { + constructorHistory.Add(key, new List { instanceModel }); + } + else + { + constructorHistory[key].Add(instanceModel); + } + + return true; + } + + /// + /// Adds to constructor history. + /// + /// The key. + /// The constructor information. + /// The arguments. + /// bool. + internal bool AddToConstructorHistory(Type key, ConstructorInfo? constructorInfo, List args) => + AddToConstructorHistory(key, new ConstructorModel(constructorInfo, args)); + + /// + /// Ensure Type is correct. + /// + /// The type. + /// Type. + internal Type CleanType(Type type) => type.Name.EndsWith('&') + ? type.Assembly.GetTypes().FirstOrDefault(x => x.Name.Equals(type.Name.TrimEnd('&'))) ?? type + : type; + /// /// Creates the HTTP client internal. /// @@ -1218,7 +1348,7 @@ internal MockModel AddMock(Mock mock, Type type, bool overwrite = false, bool no internal HttpClient CreateHttpClientInternal(Uri baseUri) => new(GetObject() ?? throw new ApplicationException("Unable to create HttpMessageHandler.")) { - BaseAddress = baseUri + BaseAddress = baseUri, }; /// @@ -1228,13 +1358,13 @@ internal HttpClient CreateHttpClientInternal(Uri baseUri) => /// The constructor function. /// The arguments. /// T. - internal T? CreateInstanceInternal(Func constructorFunc, Dictionary? data) where T : class + internal T? CreateInstanceInternal(Func constructorFunc, Dictionary? data) where T : class { var type = typeof(T).IsInterface ? GetTypeFromInterface() : new InstanceModel(); if (type.CreateFunc != null) { - return (T)type.CreateFunc.Invoke(this); + return (T) type.CreateFunc.Invoke(this); } data ??= new Dictionary(); @@ -1273,6 +1403,7 @@ internal HttpClient CreateHttpClientInternal(Uri baseUri) => /// object?. internal object? CreateInstanceInternal(Type type, ConstructorInfo? info, params object?[] args) { + AddToConstructorHistory(type, info, args.ToList()); var paramList = info?.GetParameters().ToList() ?? new(); var newArgs = args.ToList(); @@ -1309,9 +1440,12 @@ internal ConstructorModel FindConstructor(Type type, bool nonPublic, params obje ) .ToList(); - return !constructors.Any() && !nonPublic && !Strict ? FindConstructor(type, true, args) : - !constructors.Any() ? throw new NotImplementedException("Unable to find the constructor.") : - constructors.First(); + return constructors.Any() switch + { + false when !nonPublic && !Strict => FindConstructor(type, true, args), + false => throw new NotImplementedException("Unable to find the constructor."), + _ => constructors[0], + }; } /// @@ -1322,9 +1456,15 @@ internal ConstructorModel FindConstructor(Type type, bool nonPublic, params obje /// if set to true [non public]. /// Constructors to ignore. /// . - /// Multiple parameterized constructors exist. Cannot decide which to use. + /// + /// Multiple parameterized constructors exist. Cannot decide which to + /// use. + /// /// Unable to find the constructor. - /// Multiple parameterized constructors exist. Cannot decide which to use. + /// + /// Multiple parameterized constructors exist. Cannot + /// decide which to use. + /// /// Unable to find the constructor. internal ConstructorModel FindConstructor(bool bestGuess, Type type, bool nonPublic, List? excludeList = null) { @@ -1357,48 +1497,6 @@ internal ConstructorModel FindConstructor(bool bestGuess, Type type, bool nonPub return validConstructors.Last(); } - /// - /// Gets the tested constructors. - /// - /// The type to try to create. - /// The constructors to test with the specified type. - /// List<FastMoq.Models.ConstructorModel>. - private List GetTestedConstructors(Type type, List constructors) - { - constructors ??= new(); - var validConstructors = new List(); - - if (constructors.Count <= 1) - { - return constructors; - } - - var targetError = new List(); - - foreach (var constructor in constructors) - { - try - { - // Test Constructor. - var mock = CreateMockInternal(type, constructor); - _ = mock.Object; - validConstructors.Add(constructor); - } - catch (TargetInvocationException) - { - // Track invocation issues to bubble up if a good constructor is not found. - targetError.Add(constructor); - } - catch - { - // Ignore - } - } - - return validConstructors.Any() ? validConstructors : targetError; - - } - /// /// Finds the type of the constructor by. /// @@ -1411,8 +1509,12 @@ internal ConstructorInfo FindConstructorByType(Type type, bool nonPublic, params { var constructors = GetConstructorsByType(nonPublic, type, args); - return !constructors.Any() && !nonPublic && !Strict ? FindConstructorByType(type, true, args) : - !constructors.Any() ? throw new NotImplementedException("Unable to find the constructor.") : constructors.First(); + return constructors.Any() switch + { + false when !nonPublic && !Strict => FindConstructorByType(type, true, args), + false => throw new NotImplementedException("Unable to find the constructor."), + _ => constructors[0], + }; } /// @@ -1494,37 +1596,6 @@ internal List GetConstructorsNonPublic(Type type, .Select(x => new ConstructorModel(x)).ToList(); } - /// - /// Gets the database context. - /// - /// The type of the t context. - /// TContext. - public TContext GetDbContext() where TContext : DbContext, new() - { - return GetDbContext(_=> new TContext()); - } - - /// - /// Gets the database context. - /// - /// The type of the t context. - /// The new object function. - /// TContext. - public TContext GetDbContext(Func newObjectFunc) where TContext : DbContext - { - DbConnection = new SqliteConnection("DataSource=:memory:"); - DbConnection.Open(); - var dbContextOptions = new DbContextOptionsBuilder() - .UseSqlite(DbConnection) - .Options; - - var context = newObjectFunc(dbContextOptions); - context.Database.EnsureCreated(); - context.SaveChanges(); - - return context; - } - /// /// Gets the injection fields. /// @@ -1551,21 +1622,6 @@ internal static IEnumerable GetInjectionProperties(Type type, Type y.AttributeType == attributeType || y.AttributeType.Name.Equals("InjectAttribute", StringComparison.OrdinalIgnoreCase))); - /// - /// Gets the map model. - /// - /// The type of the t model. - /// FastMoq.Models.InstanceModel<TModel>?. - internal InstanceModel? GetMapModel() where TModel : class => GetMapModel(typeof(TModel)) as InstanceModel; - - /// - /// Gets the map model. - /// - /// The type. - /// FastMoq.Models.InstanceModel?. - internal InstanceModel? GetMapModel(Type type) => typeMap.ContainsKey(type) ? typeMap[type] : null; - - /// /// Gets the mock model. /// @@ -1574,10 +1630,21 @@ internal static IEnumerable GetInjectionProperties(Type type, Type /// Create Mock if it doesn't exist. /// . /// - internal MockModel GetMockModel(Type type, Mock? mock = null, bool autoCreate = true) => - mockCollection.FirstOrDefault(x => x.Type == type && (x.Mock == mock || mock == null)) ?? - (mock == null ? autoCreate ? GetMockModel(type, GetMock(type), autoCreate) : throw new NotImplementedException() : - autoCreate ? AddMock(mock, type) : throw new NotImplementedException()); + internal MockModel GetMockModel(Type type, Mock? mock = null, bool autoCreate = true) + { + var first = mockCollection.FirstOrDefault(x => x.Type == type && (x.Mock == mock || mock == null)); + if (first != null) + { + return first; + } + + if (!autoCreate) + { + throw new NotImplementedException(); + } + + return mock == null ? GetMockModel(type, GetMock(type), autoCreate) : AddMock(mock, type); + } /// /// Gets the mock model. @@ -1604,63 +1671,91 @@ internal MockModel GetMockModel(Mock? mock = null, bool autoCreate = tr /// object?. internal object? GetParameter(Type parameterType) { - if (parameterType.IsClass || parameterType.IsInterface) + if (!parameterType.IsClass && !parameterType.IsInterface) { - var typeValueModel = GetMapModel(parameterType); - if (typeValueModel?.CreateFunc != null) - { - return typeValueModel.CreateFunc.Invoke(this); - } + return GetDefaultValue(parameterType); + } - if (!parameterType.IsSealed) - { - return GetObject(parameterType); - } + var typeValueModel = GetTypeModel(parameterType); + if (typeValueModel.CreateFunc != null) + { + return typeValueModel.CreateFunc.Invoke(this); } - return GetDefaultValue(parameterType); + return !parameterType.IsSealed ? GetObject(parameterType) : GetDefaultValue(parameterType); } /// /// Gets the type from interface. /// - /// The Mock , usually an interface. - /// InstanceModel. - /// - /// - internal InstanceModel GetTypeFromInterface() where T : class + /// Type of the t. + /// Type. + /// + internal Type GetTypeFromInterface(Type tType) { - var tType = typeof(T); - if (!tType.IsInterface) { - return new InstanceModel(); + return tType; } - var mappedType = typeMap.Where(x => x.Key == typeof(T)).Select(x => x.Value).FirstOrDefault(); + var mappedType = typeMap.Where(x => x.Key == tType).Select(x => x.Value).FirstOrDefault(); if (mappedType != null) { - return mappedType; + return mappedType.InstanceType; } var types = tType.Assembly.GetTypes().ToList(); // Get interfaces that contain T. - List interfaces = types.Where(type => type.IsInterface && type.GetInterfaces().Contains(tType)).ToList(); + var interfaces = types.Where(type => type.IsInterface && type.GetInterfaces().Contains(tType)).ToList(); // Get Types that contain T, but are not interfaces. - List possibleTypes = types.Where(type => + var possibleTypes = types.Where(type => type.GetInterfaces().Contains(tType) && interfaces.All(iType => type != iType) && !interfaces.Any(iType => iType.IsAssignableFrom(type)) ).ToList(); - return new InstanceModel(possibleTypes.Count > 1 ? throw new AmbiguousImplementationException() : - !possibleTypes.Any() ? throw new NotImplementedException() : possibleTypes.First() - ); + if (possibleTypes.Count > 1) + { + throw new AmbiguousImplementationException(); + } + + return !possibleTypes.Any() ? tType : possibleTypes[0]; + } + + /// + /// Gets the type from interface. + /// + /// The Mock , usually an interface. + /// InstanceModel. + /// + /// + internal IInstanceModel GetTypeFromInterface() where T : class + { + var tType = typeof(T); + var newType = GetTypeFromInterface(tType); + var model = new InstanceModel(tType, newType) ?? throw new NotImplementedException(); + + return model; } + /// + /// Gets the map model. + /// + /// The type of the t model. + /// FastMoq.Models.InstanceModel<TModel>?. + internal IInstanceModel GetTypeModel() where TModel : class => GetTypeModel(typeof(TModel)) ?? new InstanceModel(); + + /// + /// Gets the map model. + /// + /// The type. + /// FastMoq.Models.InstanceModel?. + internal IInstanceModel GetTypeModel(Type type) => + typeMap.ContainsKey(type) ? typeMap[type] : new InstanceModel(type, GetTypeFromInterface(type)); + /// /// Determines whether [is mock file system] [the specified use predefined file system]. /// @@ -1754,5 +1849,65 @@ internal static bool IsValidConstructorByType(ConstructorInfo info, params Type? /// The type. /// internal static void ThrowAlreadyExists(Type type) => throw new ArgumentException($"{type} already exists."); + + /// + /// Creates the mock internal. + /// + /// The type to create. + /// The constructor model. + /// Mock. + private Mock CreateMockInternal(Type type, ConstructorModel constructor) + { + var newType = typeof(Mock<>).MakeGenericType(type); + + // Execute new Mock with Loose Behavior and arguments from constructor, if applicable. + var parameters = new List { Strict ? MockBehavior.Strict : MockBehavior.Loose }; + constructor?.ParameterList.ToList().ForEach(parameters.Add); + + return Activator.CreateInstance(newType, parameters.ToArray()) is not Mock oMock + ? throw new ApplicationException("Cannot create instance.") + : oMock; + } + + /// + /// Gets the tested constructors. + /// + /// The type to try to create. + /// The constructors to test with the specified type. + /// List<FastMoq.Models.ConstructorModel>. + private List GetTestedConstructors(Type type, List constructors) + { + constructors ??= new(); + var validConstructors = new List(); + + if (constructors.Count <= 1) + { + return constructors; + } + + var targetError = new List(); + + foreach (var constructor in constructors) + { + try + { + // Test Constructor. + var mock = CreateMockInternal(type, constructor); + _ = mock.Object; + validConstructors.Add(constructor); + } + catch (TargetInvocationException) + { + // Track invocation issues to bubble up if a good constructor is not found. + targetError.Add(constructor); + } + catch + { + // Ignore + } + } + + return validConstructors.Any() ? validConstructors : targetError; + } } -} +} \ No newline at end of file diff --git a/FastMoq.Core/Models/ConstructorModel.cs b/FastMoq.Core/Models/ConstructorModel.cs index 4108a4a..6c4774b 100644 --- a/FastMoq.Core/Models/ConstructorModel.cs +++ b/FastMoq.Core/Models/ConstructorModel.cs @@ -5,7 +5,7 @@ namespace FastMoq.Models /// /// Class ConstructorModel. /// - internal class ConstructorModel + public class ConstructorModel : IHistoryModel { #region Properties diff --git a/FastMoq.Core/Models/IHistoryModel.cs b/FastMoq.Core/Models/IHistoryModel.cs new file mode 100644 index 0000000..15a10bd --- /dev/null +++ b/FastMoq.Core/Models/IHistoryModel.cs @@ -0,0 +1,6 @@ +namespace FastMoq.Models +{ + public interface IHistoryModel + { + } +} diff --git a/FastMoq.Core/Models/IInstanceModel.cs b/FastMoq.Core/Models/IInstanceModel.cs new file mode 100644 index 0000000..b4a15f4 --- /dev/null +++ b/FastMoq.Core/Models/IInstanceModel.cs @@ -0,0 +1,30 @@ +namespace FastMoq.Models +{ + /// + /// Interface IInstanceModel + /// + public interface IInstanceModel : IHistoryModel + { + #region Properties + + /// + /// Gets the type. + /// + /// The type. + Type Type { get; } + + /// + /// Gets the create function. + /// + /// The create function. + Func? CreateFunc { get; } + + /// + /// Gets the type of the instance. + /// + /// The type of the instance. + Type InstanceType { get; } + + #endregion + } +} \ No newline at end of file diff --git a/FastMoq.Core/Models/InstanceModel.cs b/FastMoq.Core/Models/InstanceModel.cs index fe75042..2169761 100644 --- a/FastMoq.Core/Models/InstanceModel.cs +++ b/FastMoq.Core/Models/InstanceModel.cs @@ -2,108 +2,68 @@ namespace FastMoq.Models { - /// /// /// Class InstanceModel. - /// Implements the + /// Implements the /// - /// The type of the t class. - /// + /// + /// + /// [ExcludeFromCodeCoverage] - public class InstanceModel : InstanceModel where TClass : class + public class InstanceModel : IInstanceModel { #region Properties - /// - /// Gets or sets the create function. - /// - /// The create function. - public new Func? CreateFunc - { - get => (Func?)base.CreateFunc; - set => base.CreateFunc = value; - } - - #endregion - /// - /// - /// Initializes a new instance of the class. - /// - public InstanceModel() : this(null) { } + public virtual Type Type { get; } /// - /// - /// Initializes a new instance of the class. - /// - /// The create function. - public InstanceModel(Func? createFunc) : base(typeof(TClass), createFunc) { } + public virtual Type InstanceType { get; } /// - /// - /// Initializes a new instance of the class. - /// - /// The create function. - /// The arguments. - public InstanceModel(Func? createFunc, List arguments) : this(createFunc) => - Arguments = arguments; - } - - /// - /// Class InstanceModel. - /// Implements the - /// - /// - [ExcludeFromCodeCoverage] - public class InstanceModel - { - #region Properties - - /// - /// Gets or sets the type of the instance. - /// - /// The type of the instance. - public Type InstanceType { get; } - - /// - /// Gets or sets the create function. - /// - /// The create function. public Func? CreateFunc { get; internal set; } /// /// Gets the arguments. /// /// The arguments. - public List Arguments { get; internal set; } = new(); + public List Arguments { get; internal set; } = new(); #endregion /// /// Initializes a new instance of the class. /// + /// Type of the original. /// Type of the instance. /// instanceType - internal InstanceModel(Type instanceType) => + internal InstanceModel(Type originalType, Type instanceType) + { + Type = originalType; InstanceType = instanceType ?? throw new ArgumentNullException(nameof(instanceType)); + } - /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// Type of the original. /// Type of the instance. - /// - /// arguments - internal InstanceModel(Type instanceType, Func? createFunc) : this(instanceType) => CreateFunc = createFunc; - + /// The create function. /// + internal InstanceModel(Type originalType, Type instanceType, Func? createFunc) : this(originalType, instanceType) + => CreateFunc = createFunc; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// Type of the original. /// Type of the instance. - /// + /// The create function. /// The arguments. - /// arguments - internal InstanceModel(Type instanceType, Func? createFunc, List arguments) : this(instanceType, createFunc) => Arguments = arguments ?? throw new ArgumentNullException(nameof(arguments)); + /// arguments + /// + internal InstanceModel(Type originalType, Type instanceType, Func? createFunc, List arguments) + : this(originalType, instanceType, createFunc) => + Arguments = arguments ?? throw new ArgumentNullException(nameof(arguments)); } } \ No newline at end of file diff --git a/FastMoq.Core/Models/InstanceModelT.cs b/FastMoq.Core/Models/InstanceModelT.cs new file mode 100644 index 0000000..a7282d6 --- /dev/null +++ b/FastMoq.Core/Models/InstanceModelT.cs @@ -0,0 +1,61 @@ +using System.Diagnostics.CodeAnalysis; + +namespace FastMoq.Models +{ + /// + /// + /// Class InstanceModel. + /// Implements the + /// + /// The type of the t class. + /// + [ExcludeFromCodeCoverage] + public class InstanceModel : InstanceModel where TClass : class + { + #region Properties + + /// + public override Type InstanceType => typeof(TClass); + + /// + /// Gets or sets the create function. + /// + /// The create function. + public new Func? CreateFunc + { + get => (Func?) base.CreateFunc; + set => base.CreateFunc = value; + } + + #endregion + + /// + /// + /// Initializes a new instance of the class. + /// + public InstanceModel() : this(default(Func)) { } + + /// + /// + /// Initializes a new instance of the class. + /// + /// The create function. + public InstanceModel(Func? createFunc) : base(typeof(TClass), typeof(TClass), createFunc) { } + + /// + /// + /// Initializes a new instance of the class. + /// + /// The create function. + /// The arguments. + public InstanceModel(Func? createFunc, List arguments) : this(createFunc) => + Arguments = arguments; + + /// + /// + /// Initializes a new instance of the class. + /// + /// The model. + public InstanceModel(InstanceModel model) : this(model.CreateFunc as Func, model.Arguments) { } + } +} diff --git a/FastMoq.Tests/InstanceModelTests.cs b/FastMoq.Tests/InstanceModelTests.cs index 243f6d4..2f8a594 100644 --- a/FastMoq.Tests/InstanceModelTests.cs +++ b/FastMoq.Tests/InstanceModelTests.cs @@ -28,10 +28,10 @@ public void Create() [Fact] public void CreateNullType() { - Action a = () => _ = new InstanceModel(null) { CreateFunc = _ => new FileSystem() }; + Action a = () => _ = new InstanceModel(null, null) { CreateFunc = _ => new FileSystem() }; a.Should().Throw(); - var im = new InstanceModel(null); + var im = new InstanceModel(default(Func)); im.InstanceType.Should().Be(typeof(IFileSystem)); im.CreateFunc.Should().BeNull(); } @@ -39,12 +39,12 @@ public void CreateNullType() [Fact] public void CreateInstance() { - var obj = new InstanceModel(typeof(IFileSystem), mocker => new FileSystem(), new List()); + var obj = new InstanceModel(typeof(IFileSystem), typeof(FileSystem), mocker => new FileSystem(), new List()); obj.Should().NotBeNull(); obj.CreateFunc.Should().NotBeNull(); obj.Arguments.Should().HaveCount(0); - new Action(() => new InstanceModel(typeof(IFileSystem), mocker => new FileSystem(), null)).Should().Throw(); + new Action(() => new InstanceModel(typeof(IFileSystem), typeof(FileSystem), mocker => new FileSystem(), null)).Should().Throw(); } } } \ No newline at end of file diff --git a/FastMoq.Tests/MocksTests.cs b/FastMoq.Tests/MocksTests.cs index 87a8eeb..44c7444 100644 --- a/FastMoq.Tests/MocksTests.cs +++ b/FastMoq.Tests/MocksTests.cs @@ -157,6 +157,14 @@ public void Create_WithMapTest1() o.Should().BeOfType(); } + [Fact] + public void Create_WithMapTest1b() + { + Mocks.AddType(); + var o = Mocks.CreateInstance(); + o.Should().BeOfType(); + } + [Fact] public void Create_WithMapTest2() { @@ -365,10 +373,14 @@ public void AddTypeT_ShouldBe_AddType() { Mocks.AddType(_ => Mocks.CreateInstance()); var t = Mocks.typeMap.First().Value.CreateFunc.Invoke(null); + var o = Mocks.CreateInstance(); + o.Should().BeEquivalentTo(t); Mocks.typeMap.Clear(); Mocks.AddType(typeof(TestClassOne), typeof(TestClassOne), _=> Mocks.CreateInstance()); var t2 = Mocks.typeMap.First().Value.CreateFunc.Invoke(null); t.Should().BeEquivalentTo(t2); + o = Mocks.CreateInstance(); + o.Should().BeEquivalentTo(t2); } [Fact] @@ -605,11 +617,19 @@ public void GetObject_InitAction() { var obj = Component.GetObject(t => t.field2 = 3); obj.field2.Should().Be(3); - + Component.AddType(); var obj2 = Component.GetObject(t => t.Value = 333.333); obj2.Value.Should().Be(333.333); } + [Fact] + public void GetObject_InitAction_ShouldThrowWithoutMap() + { + var obj = Component.GetObject(t => t.field2 = 3); + obj.field2.Should().Be(3); + new Action (() => Component.GetObject(t => t.Value = 333.333)).Should().Throw(); + } + [Fact] public void GetObject_ShouldThrow() { @@ -724,14 +744,14 @@ public void Mocker_CreateMockInstanceNull_ShouldThrow() => [Fact] public void Mocker_CreateWithEmptyMap() { - var test = new Mocker(new Dictionary()); + var test = new Mocker(new Dictionary()); test.typeMap.Should().BeEmpty(); } [Fact] public void Mocker_CreateWithMap() { - var map = new Dictionary + var map = new Dictionary { {typeof(IFileSystem), new InstanceModel()}, {typeof(IFile), new InstanceModel(_ => new MockFileSystem().File)}