Skip to content

Commit 20a9416

Browse files
committed
docs: add documentation for CreateAssertion attribute and its usage
1 parent ebed4b5 commit 20a9416

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
sidebar_position: 4
3+
---
4+
5+
# CreateAssertion Attribute
6+
7+
The `[CreateAssertion<T>]` attribute provides a powerful way to automatically generate assertion extension methods from existing methods that return boolean values. This approach eliminates boilerplate code and ensures consistency across your assertion library.
8+
9+
## Overview
10+
11+
The `[CreateAssertion<T>]` attribute is a source generator feature that:
12+
- Automatically creates assertion extension methods from existing boolean-returning methods
13+
- Supports both instance and static methods
14+
- Can generate both positive and negative assertions
15+
- Maintains full IntelliSense support and compile-time safety
16+
17+
## Basic Usage
18+
19+
### Instance Methods
20+
21+
For methods that exist on the type being asserted:
22+
23+
```csharp
24+
using TUnit.Assertions.Attributes;
25+
26+
[CreateAssertion<string>(nameof(string.StartsWith))]
27+
[CreateAssertion<string>(nameof(string.EndsWith))]
28+
[CreateAssertion<string>(nameof(string.Contains))]
29+
public static partial class StringAssertionExtensions;
30+
```
31+
32+
This generates assertion methods that can be used as:
33+
34+
```csharp
35+
await Assert.That("Hello World").StartsWith("Hello");
36+
await Assert.That("Hello World").EndsWith("World");
37+
await Assert.That("Hello World").Contains("lo Wo");
38+
```
39+
40+
### Static Methods
41+
42+
For static methods that take the asserted type as a parameter:
43+
44+
```csharp
45+
using System.IO;
46+
using TUnit.Assertions.Attributes;
47+
48+
[CreateAssertion<string>(typeof(Path), nameof(Path.IsPathRooted), CustomName = "IsRootedPath")]
49+
public static partial class PathAssertionExtensions;
50+
```
51+
52+
Usage:
53+
```csharp
54+
await Assert.That(@"C:\Users\Documents").IsRootedPath();
55+
```
56+
57+
## Advanced Features
58+
59+
### Custom Names
60+
61+
You can specify custom names for generated methods using the `CustomName` property:
62+
63+
```csharp
64+
[CreateAssertion<char>(nameof(char.IsDigit))]
65+
[CreateAssertion<char>(nameof(char.IsDigit), CustomName = "IsNumeric")] // Alias
66+
public static partial class CharAssertionExtensions;
67+
```
68+
69+
### Negative Assertions
70+
71+
Generate negative assertions by setting `NegateLogic = true`:
72+
73+
```csharp
74+
[CreateAssertion<string>(nameof(string.Contains))]
75+
[CreateAssertion<string>(nameof(string.Contains), CustomName = "DoesNotContain", NegateLogic = true)]
76+
public static partial class StringAssertionExtensions;
77+
```
78+
79+
Usage:
80+
```csharp
81+
await Assert.That("Hello").Contains("ell"); // Passes
82+
await Assert.That("Hello").DoesNotContain("xyz"); // Passes
83+
```
84+
85+
### Multiple Assertions on One Class
86+
87+
You can apply multiple attributes to generate a comprehensive set of assertions:
88+
89+
```csharp
90+
[CreateAssertion<char>(nameof(char.IsDigit))]
91+
[CreateAssertion<char>(nameof(char.IsDigit), CustomName = "IsNotDigit", NegateLogic = true)]
92+
[CreateAssertion<char>(nameof(char.IsLetter))]
93+
[CreateAssertion<char>(nameof(char.IsLetter), CustomName = "IsNotLetter", NegateLogic = true)]
94+
[CreateAssertion<char>(nameof(char.IsLetterOrDigit))]
95+
[CreateAssertion<char>(nameof(char.IsLetterOrDigit), CustomName = "IsNotLetterOrDigit", NegateLogic = true)]
96+
[CreateAssertion<char>(nameof(char.IsWhiteSpace))]
97+
[CreateAssertion<char>(nameof(char.IsWhiteSpace), CustomName = "IsNotWhiteSpace", NegateLogic = true)]
98+
public static partial class CharAssertionExtensions;
99+
```
100+
101+
## Attribute Properties
102+
103+
### Constructor Parameters
104+
105+
1. **Single Parameter Constructor** - For instance methods on the target type:
106+
```csharp
107+
[CreateAssertion<TTarget>(string methodName)]
108+
```
109+
110+
2. **Two Parameter Constructor** - For static methods on a different type:
111+
```csharp
112+
[CreateAssertion<TTarget>(Type containingType, string methodName)]
113+
```
114+
115+
### Optional Properties
116+
117+
- **`CustomName`**: Override the generated method name
118+
- **`NegateLogic`**: Invert the boolean result for negative assertions
119+
- **`RequiresGenericTypeParameter`**: For methods that need generic type handling
120+
- **`TreatAsInstance`**: Force treating a static method as instance (useful for extension methods)
121+
122+
## Complete Examples
123+
124+
### Example 1: DateTime Assertions
125+
126+
```csharp
127+
using System;
128+
using TUnit.Assertions.Attributes;
129+
130+
[CreateAssertion<DateTime>(nameof(DateTime.IsLeapYear), CustomName = "IsInLeapYear")]
131+
[CreateAssertion<DateTime>(nameof(DateTime.IsLeapYear), CustomName = "IsNotInLeapYear", NegateLogic = true)]
132+
[CreateAssertion<DateTime>(nameof(DateTime.IsDaylightSavingTime))]
133+
[CreateAssertion<DateTime>(nameof(DateTime.IsDaylightSavingTime), CustomName = "IsNotDaylightSavingTime", NegateLogic = true)]
134+
public static partial class DateTimeAssertionExtensions;
135+
```
136+
137+
### Example 2: File System Assertions
138+
139+
```csharp
140+
using System.IO;
141+
using TUnit.Assertions.Attributes;
142+
143+
[CreateAssertion<FileInfo>(nameof(FileInfo.Exists))]
144+
[CreateAssertion<FileInfo>(nameof(FileInfo.Exists), CustomName = "DoesNotExist", NegateLogic = true)]
145+
[CreateAssertion<FileInfo>(nameof(FileInfo.IsReadOnly))]
146+
[CreateAssertion<FileInfo>(nameof(FileInfo.IsReadOnly), CustomName = "IsNotReadOnly", NegateLogic = true)]
147+
public static partial class FileInfoAssertionExtensions;
148+
```
149+
150+
Usage:
151+
```csharp
152+
var file = new FileInfo(@"C:\temp\test.txt");
153+
await Assert.That(file).Exists();
154+
await Assert.That(file).IsNotReadOnly();
155+
```
156+
157+
### Example 3: Custom Type Assertions
158+
159+
```csharp
160+
// Your custom type
161+
public class User
162+
{
163+
public bool IsActive { get; set; }
164+
public bool IsVerified { get; set; }
165+
public bool HasPremiumAccess() => /* logic */;
166+
}
167+
168+
// Assertion extensions
169+
[CreateAssertion<User>(nameof(User.IsActive))]
170+
[CreateAssertion<User>(nameof(User.IsActive), CustomName = "IsInactive", NegateLogic = true)]
171+
[CreateAssertion<User>(nameof(User.IsVerified))]
172+
[CreateAssertion<User>(nameof(User.HasPremiumAccess))]
173+
public static partial class UserAssertionExtensions;
174+
175+
// Usage
176+
var user = new User { IsActive = true, IsVerified = false };
177+
await Assert.That(user).IsActive();
178+
await Assert.That(user).IsNotVerified();
179+
```
180+
181+
## Benefits
182+
183+
1. **Reduced Boilerplate**: No need to write repetitive assertion methods
184+
2. **Consistency**: All generated assertions follow the same pattern
185+
3. **Type Safety**: Full compile-time checking and IntelliSense support
186+
4. **Maintainability**: Changes to the source method signature are automatically reflected
187+
5. **Performance**: Source-generated code has no runtime overhead
188+
189+
## Requirements
190+
191+
- The target method must return a `bool`
192+
- The containing class must be `partial`
193+
- The containing class must be `static` for extension methods
194+
- The method parameters must be compatible with the assertion pattern
195+
196+
## Migration from Manual Assertions
197+
198+
If you have existing manual assertion methods, you can gradually migrate to using `[CreateAssertion<T>]`:
199+
200+
```csharp
201+
// Before - Manual implementation
202+
public static InvokableValueAssertionBuilder<string> StartsWith(
203+
this IValueSource<string> valueSource,
204+
string expected)
205+
{
206+
return valueSource.RegisterAssertion(
207+
new StringStartsWithCondition(expected),
208+
[expected]);
209+
}
210+
211+
// After - Using CreateAssertion
212+
[CreateAssertion<string>(nameof(string.StartsWith))]
213+
public static partial class StringAssertionExtensions;
214+
```
215+
216+
## Best Practices
217+
218+
1. **Group Related Assertions**: Keep assertions for similar types in the same partial class
219+
2. **Consistent Naming**: Use `CustomName` to maintain consistent naming patterns
220+
3. **Provide Both Positive and Negative**: Where it makes sense, provide both forms
221+
4. **Document Complex Cases**: Add XML documentation comments to the partial class for complex scenarios
222+
5. **Test Generated Code**: Ensure generated assertions behave as expected
223+
224+
## Limitations
225+
226+
- Only works with methods that return `bool`
227+
- Cannot handle methods with complex parameter patterns
228+
- Generic constraints on the method itself may require manual implementation
229+
- Methods with optional parameters may need special handling
230+
231+
For cases that can't be handled by `[CreateAssertion<T>]`, you can still write manual assertion methods alongside the generated ones in the same partial class.

0 commit comments

Comments
 (0)