Skip to content

Commit 400c1db

Browse files
Add support for FluentAssertions 7
1 parent f82d6b5 commit 400c1db

14 files changed

+641
-0
lines changed

.github/workflows/dotnet.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ jobs:
5353
dotnet restore -p:Version=${{ steps.shortversion.outputs.shortversion }}
5454
dotnet test --no-restore -c Release Serilog.Sinks.InMemory.Assertions.Tests.Integration.FluentAssertions6.csproj
5555
56+
- name: Run integration tests for FluentAssertions 7
57+
working-directory: test/Serilog.Sinks.InMemory.Assertions.Tests.Integration.FluentAssertions7
58+
run: |
59+
dotnet restore -p:Version=${{ steps.shortversion.outputs.shortversion }}
60+
dotnet test --no-restore -c Release Serilog.Sinks.InMemory.Assertions.Tests.Integration.FluentAssertions7.csproj
61+
5662
- name: Run integration tests for FluentAssertions 8
5763
working-directory: test/Serilog.Sinks.InMemory.Assertions.Tests.Integration.FluentAssertions8
5864
run: |

SerilogSinksInMemory.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Serilog.Sinks.InMemory.Asse
5151
test\Serilog.Sinks.InMemory.Assertions.Tests.Unit\WhenAssertingStructuredLogPropertyExists.cs = test\Serilog.Sinks.InMemory.Assertions.Tests.Unit\WhenAssertingStructuredLogPropertyExists.cs
5252
EndProjectSection
5353
EndProject
54+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.InMemory.FluentAssertions7", "src\Serilog.Sinks.InMemory.FluentAssertions7\Serilog.Sinks.InMemory.FluentAssertions7.csproj", "{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}"
55+
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7", "test\Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7\Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7.csproj", "{63DF819E-0BF0-40EE-B94B-515BCF8041B9}"
57+
EndProject
5458
Global
5559
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5660
Debug|Any CPU = Debug|Any CPU
@@ -181,6 +185,30 @@ Global
181185
{9E980ECF-E1CF-4121-83D2-800441F98EF7}.Release|x64.Build.0 = Release|Any CPU
182186
{9E980ECF-E1CF-4121-83D2-800441F98EF7}.Release|x86.ActiveCfg = Release|Any CPU
183187
{9E980ECF-E1CF-4121-83D2-800441F98EF7}.Release|x86.Build.0 = Release|Any CPU
188+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
189+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
190+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|x64.ActiveCfg = Debug|Any CPU
191+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|x64.Build.0 = Debug|Any CPU
192+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|x86.ActiveCfg = Debug|Any CPU
193+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Debug|x86.Build.0 = Debug|Any CPU
194+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
195+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|Any CPU.Build.0 = Release|Any CPU
196+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|x64.ActiveCfg = Release|Any CPU
197+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|x64.Build.0 = Release|Any CPU
198+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|x86.ActiveCfg = Release|Any CPU
199+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3}.Release|x86.Build.0 = Release|Any CPU
200+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
201+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
202+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|x64.ActiveCfg = Debug|Any CPU
203+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|x64.Build.0 = Debug|Any CPU
204+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|x86.ActiveCfg = Debug|Any CPU
205+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Debug|x86.Build.0 = Debug|Any CPU
206+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
207+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|Any CPU.Build.0 = Release|Any CPU
208+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|x64.ActiveCfg = Release|Any CPU
209+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|x64.Build.0 = Release|Any CPU
210+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|x86.ActiveCfg = Release|Any CPU
211+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9}.Release|x86.Build.0 = Release|Any CPU
184212
EndGlobalSection
185213
GlobalSection(SolutionProperties) = preSolution
186214
HideSolutionNode = FALSE
@@ -197,6 +225,8 @@ Global
197225
{6718197F-6C67-4534-9296-36E0F120EB47} = {B73801C2-972F-48CD-A47A-87A4544953E2}
198226
{9E980ECF-E1CF-4121-83D2-800441F98EF7} = {B73801C2-972F-48CD-A47A-87A4544953E2}
199227
{95F4D271-188F-48F9-BDB8-FD59BE4BFB30} = {056C07B9-CAD5-4F92-8A24-FBDFDCBA0DDD}
228+
{3EDF7A6B-C2D5-462C-898A-4293B3BE45D3} = {B73801C2-972F-48CD-A47A-87A4544953E2}
229+
{63DF819E-0BF0-40EE-B94B-515BCF8041B9} = {056C07B9-CAD5-4F92-8A24-FBDFDCBA0DDD}
200230
EndGlobalSection
201231
GlobalSection(ExtensibilityGlobals) = postSolution
202232
SolutionGuid = {7573ED42-AA91-4C5A-8355-F60D23EC57B1}

src/Serilog.Sinks.InMemory.Assertions/Serilog.Sinks.InMemory.Assertions.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
<None Include="..\Serilog.Sinks.InMemory.FluentAssertions6\bin\$(Configuration)\$(TargetFramework)\Serilog.Sinks.InMemory.FluentAssertions6.dll" Pack="true" PackagePath="lib\netstandard2.0\">
5252
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5353
</None>
54+
<None Include="..\Serilog.Sinks.InMemory.FluentAssertions7\bin\$(Configuration)\$(TargetFramework)\Serilog.Sinks.InMemory.FluentAssertions7.dll" Pack="true" PackagePath="lib\netstandard2.0\">
55+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
56+
</None>
5457
<None Include="..\Serilog.Sinks.InMemory.FluentAssertions8\bin\$(Configuration)\$(TargetFramework)\Serilog.Sinks.InMemory.FluentAssertions8.dll" Pack="true" PackagePath="lib\netstandard2.0\">
5558
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5659
</None>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Linq;
2+
using FluentAssertions.Execution;
3+
using FluentAssertions.Primitives;
4+
using Serilog.Sinks.InMemory.Assertions;
5+
6+
namespace Serilog.Sinks.InMemory.FluentAssertions7
7+
{
8+
public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions<InMemorySink, InMemorySinkAssertionsImpl>, InMemorySinkAssertions
9+
{
10+
public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance)
11+
{
12+
}
13+
14+
/*
15+
* Hack attack.
16+
*
17+
* This is a bit of a dirty way to work around snapshotting the InMemorySink instance
18+
* to ensure that you won't get hit by an InvalidOperationException when calling
19+
* HaveMessage() and the logger gets called from somewhere else and adds a new
20+
* LogEvent to the collection while that method is invoked.
21+
*
22+
* For now we copy the LogEvents from the current sink and use reflection to assign
23+
* it to a new instance of InMemorySink that will be used by the assertions,
24+
* effectively creating a snapshot of the InMemorySink that was used by the tests.
25+
*/
26+
private static InMemorySink SnapshotOf(InMemorySink instance)
27+
{
28+
return instance.Snapshot();
29+
}
30+
31+
protected override string Identifier => nameof(InMemorySink);
32+
33+
public LogEventsAssertions HaveMessage(
34+
string messageTemplate,
35+
string because = "",
36+
params object[] becauseArgs)
37+
{
38+
var matches = Subject
39+
.LogEvents
40+
.Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate)
41+
.ToList();
42+
43+
Execute.Assertion
44+
.BecauseOf(because, becauseArgs)
45+
.ForCondition(matches.Any())
46+
.FailWith(
47+
"Expected message {0} to be logged",
48+
messageTemplate);
49+
50+
return new LogEventsAssertionsImpl(messageTemplate, matches);
51+
}
52+
53+
public PatternLogEventsAssertions HaveMessage()
54+
{
55+
return new PatternLogEventsAssertionsImpl(Subject.LogEvents);
56+
}
57+
58+
public void NotHaveMessage(
59+
string messageTemplate = null,
60+
string because = "",
61+
params object[] becauseArgs)
62+
{
63+
int count;
64+
string failureMessage;
65+
66+
if (messageTemplate != null)
67+
{
68+
count = Subject
69+
.LogEvents
70+
.Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate);
71+
72+
failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}";
73+
}
74+
else
75+
{
76+
count = Subject
77+
.LogEvents
78+
.Count();
79+
80+
failureMessage = $"Expected no messages to be logged, but found {(count > 1 ? $"{count} messages" : "message")}";
81+
}
82+
83+
Execute.Assertion
84+
.BecauseOf(because, becauseArgs)
85+
.ForCondition(count == 0)
86+
.FailWith(failureMessage);
87+
}
88+
}
89+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using FluentAssertions;
3+
using FluentAssertions.Execution;
4+
using FluentAssertions.Primitives;
5+
using Serilog.Events;
6+
using Serilog.Sinks.InMemory.Assertions;
7+
8+
namespace Serilog.Sinks.InMemory.FluentAssertions7
9+
{
10+
public class LogEventAssertionImpl : ReferenceTypeAssertions<LogEvent, LogEventAssertionImpl>, LogEventAssertion
11+
{
12+
private readonly string _messageTemplate;
13+
14+
public LogEventAssertionImpl(string messageTemplate, LogEvent subject) : base(subject)
15+
{
16+
_messageTemplate = messageTemplate;
17+
}
18+
19+
protected override string Identifier => "log event";
20+
21+
public LogEventPropertyValueAssertions WithProperty(string name, string because = "", params object[] becauseArgs)
22+
{
23+
if (name.StartsWith("@"))
24+
{
25+
name = name.Substring(1);
26+
}
27+
28+
Execute.Assertion
29+
.BecauseOf(because, becauseArgs)
30+
.ForCondition(Subject.Properties.ContainsKey(name))
31+
.FailWith("Expected message {0} to have a property {1} but it wasn't found",
32+
_messageTemplate,
33+
name);
34+
35+
return new LogEventPropertyValueAssertionsImpl(
36+
this,
37+
Subject.Properties[name],
38+
name);
39+
}
40+
41+
public LogEventAssertion WithLevel(LogEventLevel level, string because = "", params object[] becauseArgs)
42+
{
43+
Execute.Assertion
44+
.BecauseOf(because, becauseArgs)
45+
.ForCondition(Subject.Level == level)
46+
.FailWith("Expected message {0} to have level {1}, but it is {2}",
47+
_messageTemplate,
48+
level.ToString(),
49+
Subject.Level.ToString());
50+
51+
return this;
52+
}
53+
54+
public void Match(Func<LogEvent, bool> predicate)
55+
{
56+
Subject.Should().Match<LogEvent>(o => predicate(o));
57+
}
58+
}
59+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using FluentAssertions;
3+
using FluentAssertions.Execution;
4+
using FluentAssertions.Primitives;
5+
using Serilog.Events;
6+
using Serilog.Sinks.InMemory.Assertions;
7+
8+
namespace Serilog.Sinks.InMemory.FluentAssertions7
9+
{
10+
public class LogEventPropertyValueAssertionsImpl : ReferenceTypeAssertions<LogEventPropertyValue, LogEventPropertyValueAssertionsImpl>, LogEventPropertyValueAssertions
11+
{
12+
private readonly LogEventAssertionImpl _logEventAssertion;
13+
14+
public LogEventPropertyValueAssertionsImpl(LogEventAssertionImpl logEventAssertion, LogEventPropertyValue instance, string propertyName)
15+
: base(instance)
16+
{
17+
_logEventAssertion = logEventAssertion;
18+
Identifier = propertyName;
19+
}
20+
21+
protected override string Identifier { get; }
22+
23+
public TValue WhichValue<TValue>()
24+
{
25+
if (Subject is ScalarValue scalarValue)
26+
{
27+
Execute.Assertion
28+
.ForCondition(scalarValue.Value is TValue)
29+
.FailWith("Expected property value to be of type {0} but found {1}",
30+
typeof(TValue).Name,
31+
scalarValue.Value.GetType().Name);
32+
33+
return (TValue)scalarValue.Value;
34+
}
35+
36+
throw new Exception($"Expected property value to be of type {typeof(TValue).Name} but the property value is not a scalar and I don't know how to handle that");
37+
}
38+
39+
public AndConstraint<LogEventAssertion> WithValue(object value, string because = "", params object[] becauseArgs)
40+
{
41+
var actualValue = GetValueFromProperty(Subject);
42+
43+
Execute.Assertion
44+
.BecauseOf(because, becauseArgs)
45+
.ForCondition(Equals(actualValue, value))
46+
.FailWith("Expected property {0} to have value {1} but found {2}",
47+
Identifier,
48+
value,
49+
actualValue);
50+
51+
return new AndConstraint<LogEventAssertion>(_logEventAssertion);
52+
}
53+
54+
private object GetValueFromProperty(LogEventPropertyValue instance)
55+
{
56+
switch(instance)
57+
{
58+
case ScalarValue scalarValue:
59+
return scalarValue.Value;
60+
default:
61+
return Subject.ToString();
62+
}
63+
}
64+
65+
public StructuredValueAssertions HavingADestructuredObject(string because = "", params object[] becauseArgs)
66+
{
67+
Execute.Assertion
68+
.BecauseOf(because, becauseArgs)
69+
.ForCondition(Subject is StructureValue)
70+
.FailWith("Expected message \"{0}\" to have a property {1} that holds a destructured object but found a scalar value",
71+
_logEventAssertion.Subject.MessageTemplate,
72+
Identifier);
73+
74+
return new StructuredValueAssertionsImpl(Subject as StructureValue, Identifier, _logEventAssertion);
75+
}
76+
}
77+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using FluentAssertions.Execution;
4+
using FluentAssertions.Primitives;
5+
using Serilog.Events;
6+
using Serilog.Sinks.InMemory.Assertions;
7+
8+
namespace Serilog.Sinks.InMemory.FluentAssertions7
9+
{
10+
public class LogEventsAssertionsImpl : ReferenceTypeAssertions<IEnumerable<LogEvent>, LogEventsAssertionsImpl>, LogEventsAssertions
11+
{
12+
private readonly string _messageTemplate;
13+
14+
public LogEventsAssertionsImpl(string messageTemplate, IEnumerable<LogEvent> matches) : base(matches)
15+
{
16+
_messageTemplate = messageTemplate;
17+
}
18+
19+
protected override string Identifier { get; } = "log events";
20+
21+
public LogEventsAssertions Appearing()
22+
{
23+
return this;
24+
}
25+
26+
public LogEventAssertion Once(string because = "", params object[] becauseArgs)
27+
{
28+
Execute.Assertion
29+
.BecauseOf(because, becauseArgs)
30+
.ForCondition(Subject.Count() == 1)
31+
.FailWith(
32+
"Expected message {0} to appear exactly once, but it was found {1} times",
33+
_messageTemplate,
34+
Subject.Count());
35+
36+
return new LogEventAssertionImpl(_messageTemplate, Subject.Single());
37+
}
38+
39+
public LogEventsAssertions Times(int number, string because = "", params object[] becauseArgs)
40+
{
41+
Execute.Assertion
42+
.BecauseOf(because, becauseArgs)
43+
.ForCondition(Subject.Count() == number)
44+
.FailWith(
45+
"Expected message {0} to appear {1} times, but it was found {2} times",
46+
_messageTemplate,
47+
number,
48+
Subject.Count());
49+
50+
return this;
51+
}
52+
53+
public LogEventsAssertions WithLevel(LogEventLevel level, string because = "", params object[] becauseArgs)
54+
{
55+
var notMatched = Subject.Where(logEvent => logEvent.Level != level).ToList();
56+
57+
var notMatchedText = "";
58+
59+
if(notMatched.Any())
60+
{
61+
notMatchedText = string.Join(" and ",
62+
notMatched
63+
.GroupBy(logEvent => logEvent.Level,
64+
logEvent => logEvent,
65+
(key, values) => $"{values.Count()} with level \"{key}\""));
66+
}
67+
68+
Execute.Assertion
69+
.BecauseOf(because, becauseArgs)
70+
.ForCondition(Subject.All(logEvent => logEvent.Level == level))
71+
.FailWith($"Expected instances of log message {{0}} to have level {{1}}, but found {notMatchedText}",
72+
_messageTemplate,
73+
level.ToString());
74+
75+
return this;
76+
}
77+
78+
public LogEventsPropertyAssertion WithProperty(string propertyName, string because = "", params object[] becauseArgs)
79+
{
80+
Execute.Assertion
81+
.BecauseOf(because, becauseArgs)
82+
.ForCondition(Subject.All(logEvent => logEvent.Properties.ContainsKey(propertyName)))
83+
.FailWith($"Expected all instances of log message {{0}} to have property {{1}}, but it was not found",
84+
_messageTemplate,
85+
propertyName);
86+
87+
return new LogEventsPropertyAssertionImpl(this, propertyName);
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)