|
| 1 | +--- |
| 2 | +description: 'Writing Unit Tests in Savvyio' |
| 3 | +applyTo: "**/*.{cs,csproj}" |
| 4 | +--- |
| 5 | + |
| 6 | +# Writing Unit Tests in Savvyio |
| 7 | + |
| 8 | +This document provides instructions for writing unit tests in the Savvyio codebase. Please follow these guidelines to ensure consistency and maintainability. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## 1. Base Class |
| 13 | + |
| 14 | +**Always inherit from the `Test` base class** for all unit test classes. |
| 15 | +This ensures consistent setup, teardown, and output handling across all tests. |
| 16 | + |
| 17 | +```csharp |
| 18 | +using Codebelt.Extensions.Xunit; |
| 19 | +using Xunit; |
| 20 | +using Xunit.Abstractions; |
| 21 | + |
| 22 | +namespace Your.Namespace |
| 23 | +{ |
| 24 | + public class YourTestClass : Test |
| 25 | + { |
| 26 | + public YourTestClass(ITestOutputHelper output) : base(output) |
| 27 | + { |
| 28 | + } |
| 29 | + |
| 30 | + // Your tests here |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## 2. Test Method Attributes |
| 38 | + |
| 39 | +- Use `[Fact]` for standard unit tests. |
| 40 | +- Use `[Theory]` with `[InlineData]` or other data sources for parameterized tests. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## 3. Naming Conventions |
| 45 | + |
| 46 | +- **Test classes**: End with `Test` (e.g., `CommandDispatcherTest`). |
| 47 | +- **Test methods**: Use descriptive names that state the expected behavior (e.g., `ShouldReturnTrue_WhenConditionIsMet`). |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 4. Assertions |
| 52 | + |
| 53 | +- Use `Assert` methods from xUnit for all assertions. |
| 54 | +- Prefer explicit and expressive assertions (e.g., `Assert.Equal`, `Assert.NotNull`, `Assert.Contains`). |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## 5. File and Namespace Organization |
| 59 | + |
| 60 | +- Place test files in the appropriate test project and folder structure. |
| 61 | +- Use namespaces that mirror the source code structure. |
| 62 | +- The unit tests for the Savvyio.Foo assembly live in the Savvyio.Foo.Tests assembly. |
| 63 | +- The functional tests for the Savvyio.Foo assembly live in the Savvyio.Foo.FunctionalTests assembly. |
| 64 | +- Test class names end with Test and live in the same namespace as the class being tested, e.g., the unit tests for the Boo class that resides in the Savvyio.Foo assembly would be named BooTest and placed in the Savvyio.Foo namespace in the Savvyio.Foo.Tests assembly. |
| 65 | +- Modify the associated .csproj file to override the root namespace, e.g., <RootNamespace>Savvyio.Foo</RootNamespace>. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## 6. Example Test |
| 70 | + |
| 71 | +```csharp |
| 72 | +using Codebelt.Extensions.Xunit; |
| 73 | +using Xunit; |
| 74 | +using Xunit.Abstractions; |
| 75 | + |
| 76 | +namespace Savvyio.Commands |
| 77 | +{ |
| 78 | + /// <summary> |
| 79 | + /// Tests for the <see cref="DefaultCommand"/> class. |
| 80 | + /// </summary> |
| 81 | + public class CommandTest : Test |
| 82 | + { |
| 83 | + public CommandTest(ITestOutputHelper output) : base(output) |
| 84 | + { |
| 85 | + } |
| 86 | + |
| 87 | + [Fact] |
| 88 | + public void DefaultCommand_Ensure_Initialization_Defaults() |
| 89 | + { |
| 90 | + var sut = new DefaultCommand(); |
| 91 | + |
| 92 | + Assert.IsAssignableFrom<Command>(sut); |
| 93 | + Assert.IsAssignableFrom<ICommand>(sut); |
| 94 | + Assert.IsAssignableFrom<Request>(sut); |
| 95 | + Assert.IsAssignableFrom<IRequest>(sut); |
| 96 | + Assert.IsAssignableFrom<IMetadata>(sut); |
| 97 | + Assert.Contains(sut.Metadata, pair => pair.Key == MetadataDictionary.CorrelationId); |
| 98 | + } |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +## 7. Additional Guidelines |
| 106 | + |
| 107 | +- Keep tests focused and isolated. |
| 108 | +- Do not rely on external systems except for xUnit itself and Codebelt.Extensions.Xunit (and derived from this). |
| 109 | +- Ensure tests are deterministic and repeatable. |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +For further examples, refer to existing test files such as |
| 114 | +[`test/Savvyio.Commands.Tests/CommandDispatcherTest.cs`](test/Savvyio.Commands.Tests/CommandDispatcherTest.cs) |
| 115 | +and |
| 116 | +[`test/Savvyio.Commands.Tests/CommandTest.cs`](test/Savvyio.Commands.Tests/CommandTest.cs). |
| 117 | + |
| 118 | +--- |
| 119 | +description: 'Writing XML documentation in Savvyio' |
| 120 | +applyTo: "**/*.cs" |
| 121 | +--- |
| 122 | + |
| 123 | +# Writing XML documentation in Savvyio |
| 124 | + |
| 125 | +This document provides instructions for writing XML documentation. |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## 1. Documentation Style |
| 130 | + |
| 131 | +- Use the same documentation style as found throughout the codebase. |
| 132 | +- Add XML doc comments to public and protected classes and methods where appropriate. |
| 133 | +- Example: |
| 134 | + |
| 135 | +```csharp |
| 136 | +using Cuemon.Extensions.DependencyInjection; |
| 137 | +using Cuemon.Extensions.Text.Json.Formatters; |
| 138 | +using Microsoft.Extensions.DependencyInjection; |
| 139 | +using System; |
| 140 | +using Cuemon; |
| 141 | +using Savvyio.Extensions.Text.Json; |
| 142 | + |
| 143 | +namespace Savvyio.Extensions.DependencyInjection.Text.Json |
| 144 | +{ |
| 145 | + /// <summary> |
| 146 | + /// Extension methods for the <see cref="IServiceCollection"/> interface. |
| 147 | + /// </summary> |
| 148 | + public static class ServiceCollectionExtensions |
| 149 | + { |
| 150 | + /// <summary> |
| 151 | + /// Adds an <see cref="JsonMarshaller" /> implementation to the specified <see cref="IServiceCollection" />. |
| 152 | + /// </summary> |
| 153 | + /// <param name="services">The <see cref="IServiceCollection"/> to extend.</param> |
| 154 | + /// <param name="jsonSetup">The <see cref="JsonFormatterOptions" /> which may be configured. Default is optimized for messaging.</param> |
| 155 | + /// <param name="serviceSetup">The <see cref="ServiceOptions" /> which may be configured. Default is <see cref="ServiceLifetime.Singleton"/>.</param> |
| 156 | + /// <returns>A reference to <paramref name="services"/> so that additional calls can be chained.</returns> |
| 157 | + /// <remarks>The implementation will be type forwarded accordingly.</remarks> |
| 158 | + /// <exception cref="ArgumentNullException"> |
| 159 | + /// <paramref name="services"/> cannot be null. |
| 160 | + /// </exception> |
| 161 | + public static IServiceCollection AddJsonMarshaller(this IServiceCollection services, Action<JsonFormatterOptions> jsonSetup = null, Action<ServiceOptions> serviceSetup = null) |
| 162 | + { |
| 163 | + Validator.ThrowIfNull(services); |
| 164 | + return services |
| 165 | + .AddMarshaller<JsonMarshaller>(serviceSetup) |
| 166 | + .AddConfiguredOptions(jsonSetup ?? (o => o.Settings.WriteIndented = false)); |
| 167 | + } |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | + |
| 172 | +using System; |
| 173 | +using Cuemon; |
| 174 | +using Cuemon.Configuration; |
| 175 | + |
| 176 | +namespace Savvyio.Extensions.NATS |
| 177 | +{ |
| 178 | + /// <summary> |
| 179 | + /// Configuration options that is related to NATS. |
| 180 | + /// </summary> |
| 181 | + /// <seealso cref="IValidatableParameterObject" /> |
| 182 | + public class NatsMessageOptions : IValidatableParameterObject |
| 183 | + { |
| 184 | + /// <summary> |
| 185 | + /// Initializes a new instance of the <see cref="NatsMessageOptions"/> class with default values. |
| 186 | + /// </summary> |
| 187 | + /// <remarks> |
| 188 | + /// The following table shows the initial property values for an instance of <see cref="NatsMessageOptions"/>. |
| 189 | + /// <list type="table"> |
| 190 | + /// <listheader> |
| 191 | + /// <term>Property</term> |
| 192 | + /// <description>Initial Value</description> |
| 193 | + /// </listheader> |
| 194 | + /// <item> |
| 195 | + /// <term><see cref="NatsUrl"/></term> |
| 196 | + /// <description><c>new Uri("nats://127.0.0.1:4222")</c></description> |
| 197 | + /// </item> |
| 198 | + /// <item> |
| 199 | + /// <term><see cref="Subject"/></term> |
| 200 | + /// <description><c>null</c></description> |
| 201 | + /// </item> |
| 202 | + /// </list> |
| 203 | + /// </remarks> |
| 204 | + public NatsMessageOptions() |
| 205 | + { |
| 206 | + NatsUrl = new Uri("nats://127.0.0.1:4222"); |
| 207 | + } |
| 208 | + |
| 209 | + /// <summary> |
| 210 | + /// Gets or sets the URI of the NATS server. |
| 211 | + /// </summary> |
| 212 | + public Uri NatsUrl { get; set; } |
| 213 | + |
| 214 | + /// <summary> |
| 215 | + /// Gets or sets the subject to publish or subscribe to in NATS. |
| 216 | + /// </summary> |
| 217 | + public string Subject { get; set; } |
| 218 | + |
| 219 | + /// <summary> |
| 220 | + /// Validates the current options and throws an exception if the state is invalid. |
| 221 | + /// </summary> |
| 222 | + /// <exception cref="InvalidOperationException"> |
| 223 | + /// <see cref="Subject"/> is null or whitespace - or - |
| 224 | + /// <see cref="NatsUrl"/> is null. |
| 225 | + /// </exception> |
| 226 | + public virtual void ValidateOptions() |
| 227 | + { |
| 228 | + Validator.ThrowIfInvalidState(NatsUrl == null); |
| 229 | + Validator.ThrowIfInvalidState(string.IsNullOrWhiteSpace(Subject)); |
| 230 | + } |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +``` |
0 commit comments