@@ -28,7 +28,14 @@ Assert(items.Contains(target));
2828
2929- ** 🔍 Detailed Expression Analysis** - See exactly why your assertions failed
3030- ** 📦 Simple Setup** - Just add NuGet package, no MSBuild configuration needed
31- - ** 🔄 PowerAssert Integration** - Complete support for PowerAssert (switch option)
31+ - ** 🎯 Exception Testing** - ` Throws<T> ` and ` ThrowsAsync<T> ` with detailed exception diagnostics
32+ - ** 🔤 String Diffs** - Character-level inline diffs for strings (powered by DiffPlex)
33+ - ** 📊 Collection Comparison** - First mismatch, missing/extra elements detection
34+ - ** 🔎 Object Deep Diff** - Property-level differences for objects/records (powered by Compare-Net-Objects)
35+ - ** 🔗 LINQ Operations** - Enhanced diagnostics for Contains/Any/All operations
36+ - ** ⚡ Async/Await Support** - Full support for async assertions with value diagnostics
37+ - ** 💫 Dynamic Types** - Dynamic expression support with DLR semantics
38+ - ** 🔄 PowerAssert Integration** - Optional PowerAssert mode
3239
3340## Quick Start
3441
@@ -63,35 +70,111 @@ public void Should_be_equal()
6370Assert (user .IsActive , $" User {user .Name } should be active for this operation" );
6471```
6572
66- ### Asserting exceptions
73+ ## Features in Detail
74+
75+ ### Exception Testing
76+
77+ Test expected exceptions with ` Throws<T> ` and ` ThrowsAsync<T> ` :
6778
6879``` csharp
69- using static SharpAssert .Sharp ;
80+ // Positive assertion - expects exception
81+ Assert (Throws <ArgumentException >(() => throw new ArgumentException (" invalid" )));
7082
71- [Test ]
72- public async Task Throws_catch_exceptions_in_exception_result ()
73- {
74- // Thows returns ExceptionResult which allows using them as condition in Assert
75- Assert (Throws <ArgumentException >(()=> new ArgumentException (" foo" )));
76- Assert (Throws <ArgumentException >(()=> new ArgumentNullException (" bar" ))); // will throw unexpected exception
77- Assert (! Throws <ArgumentException >(()=> {})); // negative assertion via C# not syntax
83+ // Negative assertion - expects no exception
84+ Assert (! Throws <ArgumentException >(() => { /* no exception */ }));
7885
79- var ex = Throws <ArgumentException >(()=> new ArgumentException (" baz" )); // always returns ExceptionResult
80- Assert (ex .Exception .ArgumentName == " baz" ); // get thrown exception and assert on any custom property
86+ // Access exception properties
87+ var ex = Throws <ArgumentNullException >(() => throw new ArgumentNullException (" param" ));
88+ Assert (ex .Message .Contains (" param" ));
8189
82- Assert (Throws <ArgumentException >(()=>
83- new ArgumentException (" baz" )).Data == " baz" ); // shortcut form to assert on exception Data property
90+ // Async version
91+ Assert (await ThrowsAsync <InvalidOperationException >(() =>
92+ Task .Run (() => throw new InvalidOperationException ())));
93+ ```
8494
85- Assert (Throws <ArgumentException >(()=>
86- new ArgumentException (" bar" )).Message .Contains (" bar" )); // shortcut form to assert on exception Message
87-
88- // async version
89- Assert (await ThrowsAsync <ArgumentException >(()=>
90- Task .Run (() => throw ArgumentException (" async" ))));
95+ ### String Comparisons
9196
92- var ex = ThrowsAsync <ArgumentException >(()=> Task .Run (() => throw ArgumentException (" async" ))); // always returns ExceptionResult
93- Assert (ex .Message .Contains (" async" )); // assert on message using shortcut on ExceptionResult
94- }
97+ Character-level diffs powered by DiffPlex:
98+
99+ ``` csharp
100+ var actual = " hello" ;
101+ var expected = " hallo" ;
102+
103+ Assert (actual == expected );
104+ // Assertion failed: actual == expected
105+ // String diff (inline):
106+ // h[-e][+a]llo
107+ ```
108+
109+ Multiline string diffs:
110+
111+ ``` csharp
112+ var actual = " line1\n line2\n line3" ;
113+ var expected = " line1\n MODIFIED\n line3" ;
114+
115+ Assert (actual == expected );
116+ // Assertion failed: actual == expected
117+ // String diff:
118+ // line1
119+ // - line2
120+ // + MODIFIED
121+ // line3
122+ ```
123+
124+ ### Collection Comparisons
125+
126+ First mismatch and missing/extra elements:
127+
128+ ``` csharp
129+ var actual = new [] { 1 , 2 , 3 , 5 };
130+ var expected = new [] { 1 , 2 , 4 , 5 };
131+
132+ Assert (actual .SequenceEqual (expected ));
133+ // Assertion failed: actual.SequenceEqual(expected)
134+ // Collections differ at index 2:
135+ // Expected: 4
136+ // Actual: 3
137+ ```
138+
139+ ### Object Deep Comparison
140+
141+ Property-level diffs powered by Compare-Net-Objects:
142+
143+ ``` csharp
144+ var actual = new User { Name = " John" , Age = 30 , City = " NYC" };
145+ var expected = new User { Name = " John" , Age = 25 , City = " LA" };
146+
147+ Assert (actual == expected );
148+ // Assertion failed: actual == expected
149+ // Object differences:
150+ // Age: 30 → 25
151+ // City: "NYC" → "LA"
152+ ```
153+
154+ ### LINQ Operations
155+
156+ Enhanced diagnostics for Contains, Any, All:
157+
158+ ``` csharp
159+ var users = new [] { " Alice" , " Bob" , " Charlie" };
160+
161+ Assert (users .Contains (" David" ));
162+ // Assertion failed: users.Contains("David")
163+ // Collection: ["Alice", "Bob", "Charlie"]
164+ // Looking for: "David"
165+ // Result: false
166+ ```
167+
168+ ### Async/Await Support
169+
170+ Full support for async expressions:
171+
172+ ``` csharp
173+ Assert (await client .GetAsync () == await server .GetAsync ());
174+ // Assertion failed: await client.GetAsync() == await server.GetAsync()
175+ // Left: { Id: 1, Name: "Client" }
176+ // Right: { Id: 2, Name: "Server" }
177+ // Result: false
95178```
96179
97180## How It Works
@@ -124,39 +207,99 @@ Assert(order.Items.Length > 0 && order.Total == expectedTotal);
124207SharpAssert is built on modern .NET technologies:
125208
126209- ** MSBuild Source Rewriting** - Compile-time code transformation
127- - ** Roslyn Syntax Analysis** - Advanced C# code parsing and generation
128- - ** Expression Trees** - Runtime expression analysis
210+ - ** Roslyn Syntax Analysis** - Advanced C# code parsing and generation
211+ - ** Expression Trees** - Runtime expression analysis for rich diagnostics
212+ - ** DiffPlex** - String and sequence diffs
213+ - ** CompareNETObjects** - Deep object comparison
214+ - ** PowerAssert** - Optional alternative assertion engine
129215- ** CallerArgumentExpression** - Fallback for edge cases
130216
131- ### PowerAssert Integration
217+ ### Requirements
218+ - .NET 9.0 or later
219+ - Test frameworks: xUnit, NUnit, or MSTest
220+
221+ ### Packages
222+ SharpAssert consists of two NuGet packages:
223+
224+ - ** SharpAssert** - Main package with MSBuild rewriter (install this one)
225+ - ** SharpAssert.Runtime** - Core assertion library (automatically included as dependency)
226+
227+ When you install ` SharpAssert ` , you get everything you need. The runtime package is a transitive dependency and requires no separate installation.
228+
229+ ## Configuration
230+
231+ ### PowerAssert Mode
232+
233+ SharpAssert includes optional PowerAssert integration. You can force PowerAssert for all assertions via MSBuild property:
234+
235+ ``` xml
236+ <PropertyGroup >
237+ <!-- Force PowerAssert for ALL assertions (optional) -->
238+ <SharpAssertUsePowerAssert >true</SharpAssertUsePowerAssert >
239+ </PropertyGroup >
240+ ```
132241
133- SharpAssert includes PowerAssert integration and also uses it as a fallback mechanism for not yet implemented features.
242+ ### Diagnostic Logging
134243
135- To force PowerAssert for all assertions :
244+ Enable detailed rewriter diagnostics :
136245
137246``` xml
138247<PropertyGroup >
139- <UsePowerAssert >true</UsePowerAssert >
248+ <!-- Enable diagnostic logging for troubleshooting rewriter issues -->
249+ <SharpAssertEmitRewriteInfo >true</SharpAssertEmitRewriteInfo >
140250</PropertyGroup >
141251```
142252
143- ## Known issues
144- -
145- - Collection initializers could not be used in expression trees. Compiler limitation. Use ` new[]{1,2,3} ` instead of ` [1, 2, 3] `
253+ ## Performance
254+
255+ SharpAssert is designed for minimal overhead:
256+
257+ - ** Passing tests** : Near-zero overhead - only the assertion check itself
258+ - ** Failing tests** : Rich diagnostics are computed only when assertions fail
259+ - ** Expression evaluation** : Each sub-expression evaluated exactly once (cached)
260+ - ** Build time** : Negligible impact - rewriter processes only test files
261+
262+ The rich diagnostic tools (object diffing, collection comparison) are ** only invoked on failure** .
263+ This means your passing tests run at full speed, and the diagnostic cost is only paid when you need to understand a failure.
264+
265+ ## Known Issues
266+
267+ - Collection initializers cannot be used in expression trees (C# compiler limitation)
268+ - Use ` new[]{1,2,3} ` instead of ` [1, 2, 3] `
269+
270+ ## Live Examples
271+
272+ Want to see all features in action? Check out the ** SharpAssert.Demo** project:
146273
274+ ``` bash
275+ cd src/SharpAssert.Demo
276+ dotnet run
277+ ```
278+
279+ See [ SharpAssert.Demo/README.md] ( src/SharpAssert.Demo/README.md ) for interactive feature exploration and generated markdown documentation.
147280
148281## Troubleshooting
149282
150283### Rewriting not working
1512841 . Verify ` SharpAssert ` package is installed (SharpAssert.Runtime comes automatically)
1522852 . Ensure ` using static SharpAssert.Sharp; ` import
286+ 3 . Clean and rebuild: ` dotnet clean && dotnet build `
153287
154288### No detailed error messages
1552891 . Check build output contains: "SharpAssert: Rewriting X source files"
1562902 . Verify rewritten files exist in ` obj/Debug/net9.0/SharpRewritten/ `
1572913 . Ensure ` SharpInternal.Assert ` calls are being made (check generated code)
1582924 . Look for #line directives in generated files
159293
294+ ### Enable diagnostic logging
295+ For troubleshooting rewriter issues:
296+ ``` xml
297+ <PropertyGroup >
298+ <SharpAssertEmitRewriteInfo >true</SharpAssertEmitRewriteInfo >
299+ </PropertyGroup >
300+ ```
301+ Then rebuild with verbose output: ` dotnet build -v detailed `
302+
160303## Contributing
161304
162305We welcome contributions! Please see our comprehensive [ Contributing Guide] ( CONTRIBUTING.md ) for:
0 commit comments