Skip to content

Commit 3dd1f75

Browse files
authored
Merge pull request #732 from zlangner/add-support-for-ilogger-extension-methods
added ILoggerTests to ensure NSubstitute can be used to test specific ILogger calls happened.
2 parents 6db536b + cb71205 commit 3dd1f75

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

src/NSubstitute/Core/CallSpecification.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,25 @@ private static Type[] ParameterTypes(MethodInfo info)
6969
return info.GetParameters().Select(p=>p.ParameterType).ToArray();
7070
}
7171

72-
private static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
72+
internal static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
7373
{
7474
if (aArgs.Length != bArgs.Length) return false;
7575
for (var i = 0; i < aArgs.Length; i++)
7676
{
7777
var first = aArgs[i];
7878
var second = bArgs[i];
7979

80+
if (first.IsGenericType && second.IsGenericType
81+
&& first.GetGenericTypeDefinition() == second.GetGenericTypeDefinition())
82+
{
83+
// both are the same generic type. If their GenericTypeArguments match then they are equivalent
84+
if (!TypesAreAllEquivalent(first.GenericTypeArguments, second.GenericTypeArguments))
85+
{
86+
return false;
87+
}
88+
continue;
89+
}
90+
8091
var areEquivalent = first.IsAssignableFrom(second) || second.IsAssignableFrom(first) ||
8192
typeof(Arg.AnyType).IsAssignableFrom(first) || typeof(Arg.AnyType).IsAssignableFrom(second);
8293
if (!areEquivalent) return false;

src/NSubstitute/Core/Extensions.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,22 @@ public static bool IsCompatibleWith(this object? instance, Type type)
2020
}
2121

2222
var requiredType = type.IsByRef ? type.GetElementType()! : type;
23-
return instance == null ? TypeCanBeNull(requiredType) : requiredType.IsInstanceOfType(instance);
23+
24+
if (instance == null)
25+
{
26+
return TypeCanBeNull(requiredType);
27+
}
28+
29+
var instanceType = instance.GetType();
30+
31+
if (instanceType.IsGenericType && type.IsGenericType
32+
&& instanceType.GetGenericTypeDefinition() == type.GetGenericTypeDefinition())
33+
{
34+
// both are the same generic type. If their GenericTypeArguments match then they are equivalent
35+
return CallSpecification.TypesAreAllEquivalent(instanceType.GenericTypeArguments, type.GenericTypeArguments);
36+
}
37+
38+
return requiredType.IsInstanceOfType(instance);
2439
}
2540

2641
/// <summary>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using Microsoft.Extensions.Logging;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace NSubstitute.Acceptance.Specs;
8+
9+
[TestFixture]
10+
public class ILoggerTests
11+
{
12+
[Test]
13+
public void Received_LogTrace_call_using_AnyType()
14+
{
15+
var logger = Substitute.For<ILogger<ILoggerTests>>();
16+
17+
logger.LogTrace("Vanished without a trace");
18+
19+
logger.Received(1)
20+
.Log(
21+
LogLevel.Trace,
22+
Arg.Any<EventId>(),
23+
Arg.Any<Arg.AnyType>(),
24+
Arg.Any<Exception>(),
25+
Arg.Any<Func<Arg.AnyType, Exception, string>>()
26+
);
27+
}
28+
29+
[Test]
30+
public void Received_LogWarning_call_using_interfaced_types()
31+
{
32+
var logger = Substitute.For<ILogger<ILoggerTests>>();
33+
34+
logger.LogWarning("Warning: Live without warning");
35+
36+
logger.Received(1)
37+
.Log(
38+
LogLevel.Warning,
39+
Arg.Any<EventId>(),
40+
Arg.Any<IReadOnlyList<KeyValuePair<string, object>>>(),
41+
Arg.Any<Exception>(),
42+
Arg.Any<Func<IReadOnlyList<KeyValuePair<string, object>>, Exception, string>>()
43+
);
44+
}
45+
46+
[Test]
47+
public void Received_LogError_call_using_messageTemplate()
48+
{
49+
var logger = Substitute.For<ILogger<ILoggerTests>>();
50+
51+
logger.LogError("Something bad happened!!!");
52+
53+
logger.Received(1)
54+
.Log(
55+
LogLevel.Error,
56+
Arg.Any<EventId>(),
57+
Arg.Is<IReadOnlyList<KeyValuePair<string, object>>>(list => list.Any(i => i.Key == "{OriginalFormat}" && i.Value.Equals("Something bad happened!!!"))),
58+
Arg.Any<Exception>(),
59+
Arg.Any<Func<IReadOnlyList<KeyValuePair<string, object>>, Exception, string>>()
60+
);
61+
62+
logger.DidNotReceive()
63+
.Log(
64+
LogLevel.Error,
65+
Arg.Any<EventId>(),
66+
Arg.Is<IReadOnlyList<KeyValuePair<string, object>>>(list => list.Any(i => i.Key == "{OriginalFormat}" && i.Value.Equals("some other message"))),
67+
Arg.Any<Exception>(),
68+
Arg.Any<Func<IReadOnlyList<KeyValuePair<string, object>>, Exception, string>>()
69+
);
70+
}
71+
72+
[Test]
73+
public void Received_LogInformation_call_using_messageTemplate()
74+
{
75+
var now = DateTimeOffset.UtcNow;
76+
var later = now.AddTicks(1);
77+
var logger = Substitute.For<ILogger<ILoggerTests>>();
78+
79+
logger.LogInformation("I have parameters. {param1} {param2} {param3}", 1979, "apocalypse", now);
80+
81+
logger.Received(1)
82+
.Log(
83+
LogLevel.Information,
84+
Arg.Any<EventId>(),
85+
Arg.Is<IReadOnlyList<KeyValuePair<string, object>>>(list =>
86+
list.Any(i => i.Key == "{OriginalFormat}" && i.Value.Equals("I have parameters. {param1} {param2} {param3}"))
87+
&& list.Any(i => i.Key == "param1" && i.Value.Equals(1979))
88+
&& list.Any(i => i.Key == "param2" && i.Value.Equals("apocalypse"))
89+
&& list.Any(i => i.Key == "param3" && i.Value.Equals(now))
90+
),
91+
Arg.Any<Exception>(),
92+
Arg.Any<Func<IReadOnlyList<KeyValuePair<string, object>>, Exception, string>>()
93+
);
94+
95+
logger.DidNotReceive()
96+
.Log(
97+
LogLevel.Information,
98+
Arg.Any<EventId>(),
99+
Arg.Is<IReadOnlyList<KeyValuePair<string, object>>>(list =>
100+
list.Any(i => i.Key == "{OriginalFormat}" && i.Value.Equals("I have parameters. {param1} {param2} {param3}"))
101+
&& list.Any(i => i.Key == "param1" && i.Value.Equals(1979))
102+
&& list.Any(i => i.Key == "param2" && i.Value.Equals("apocalypse"))
103+
&& list.Any(i => i.Key == "param3" && i.Value.Equals(later)) // this is the only one that will mismatch
104+
),
105+
Arg.Any<Exception>(),
106+
Arg.Any<Func<IReadOnlyList<KeyValuePair<string, object>>, Exception, string>>()
107+
);
108+
}
109+
}

tests/NSubstitute.Acceptance.Specs/NSubstitute.Acceptance.Specs.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
89
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
910
<PackageReference Include="NUnit" Version="3.13.3" />
1011
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />

0 commit comments

Comments
 (0)