Skip to content

Commit 8b42aad

Browse files
committed
Add tests for custom argument parsing
Introduces unit tests to verify the behavior of commands using `CustomArgumentParsing` attribute. This ensures that: - Verbs with custom parsing handle unknown arguments gracefully. - Verbs with mixed properties and custom parsing correctly parse known arguments and ignore unknown ones. - Verbs without the `CustomArgumentParsing` attribute fail as expected when unknown arguments are provided, maintaining default behavior.
1 parent 0756278 commit 8b42aad

5 files changed

Lines changed: 204 additions & 0 deletions

File tree

Neolution.DotNet.Console.UnitTests/DotNetConsoleGrammarTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,78 @@ public async Task GivenBuiltConsoleAppWithDefaultVerbWithoutProperties_WhenCalli
180180
logger.LoggedObjects["options"].ShouldBeOfType<DefaultOptionsWithoutProperties>();
181181
}
182182

183+
/// <summary>
184+
/// When calling a non-default verb with custom argument parsing and unknown arguments, it should succeed.
185+
/// </summary>
186+
/// <returns>The <see cref="Task"/>.</returns>
187+
[Fact]
188+
public async Task GivenNonDefaultVerbWithCustomArgumentParsing_WhenCallingWithUnknownArgs_ThenShouldSucceed()
189+
{
190+
// Arrange
191+
const string args = "process --custom-arg=value --another-arg";
192+
var logger = new UnitTestLogger();
193+
var servicesAssembly = Assembly.GetAssembly(typeof(ProcessCommand))!;
194+
var verbTypes = new[] { typeof(ProcessOptions) };
195+
var builder = DotNetConsole.CreateBuilderWithReference(servicesAssembly, verbTypes, args.Split(" "));
196+
builder.Services.Replace(new ServiceDescriptor(typeof(IUnitTestLogger), logger));
197+
var console = builder.Build();
198+
199+
// Act
200+
await console.RunAsync();
201+
202+
// Assert
203+
logger.LoggedObjects["options"].ShouldBeOfType<ProcessOptions>();
204+
}
205+
206+
/// <summary>
207+
/// When calling a verb with custom argument parsing and some defined properties, it should parse known args and ignore unknown ones.
208+
/// </summary>
209+
/// <returns>The <see cref="Task"/>.</returns>
210+
[Fact]
211+
public async Task GivenVerbWithCustomArgumentParsingAndProperties_WhenCallingWithMixedArgs_ThenShouldParseKnownAndIgnoreUnknown()
212+
{
213+
// Arrange
214+
const string args = "mixed --name=Test --count=5 --unknown-arg=ignored --another=value";
215+
var logger = new UnitTestLogger();
216+
var servicesAssembly = Assembly.GetAssembly(typeof(MixedCommand))!;
217+
var verbTypes = new[] { typeof(MixedOptions) };
218+
var builder = DotNetConsole.CreateBuilderWithReference(servicesAssembly, verbTypes, args.Split(" "));
219+
builder.Services.Replace(new ServiceDescriptor(typeof(IUnitTestLogger), logger));
220+
var console = builder.Build();
221+
222+
// Act
223+
await console.RunAsync();
224+
225+
// Assert
226+
var options = (MixedOptions)logger.LoggedObjects["options"];
227+
options.Name.ShouldBe("Test");
228+
options.Count.ShouldBe(5);
229+
}
230+
231+
/// <summary>
232+
/// When calling a verb without custom argument parsing attribute and unknown arguments, the command should not execute.
233+
/// This ensures the default behavior still works.
234+
/// </summary>
235+
/// <returns>The <see cref="Task"/>.</returns>
236+
[Fact]
237+
public async Task GivenVerbWithoutCustomArgumentParsing_WhenCallingWithUnknownArgs_ThenCommandShouldNotExecute()
238+
{
239+
// Arrange
240+
const string args = "echo --unknown-option=value";
241+
var logger = new UnitTestLogger();
242+
var servicesAssembly = Assembly.GetAssembly(typeof(EchoCommand))!;
243+
var verbTypes = new[] { typeof(EchoOptions) };
244+
var builder = DotNetConsole.CreateBuilderWithReference(servicesAssembly, verbTypes, args.Split(" "));
245+
builder.Services.Replace(new ServiceDescriptor(typeof(IUnitTestLogger), logger));
246+
var console = builder.Build();
247+
248+
// Act
249+
await console.RunAsync();
250+
251+
// Assert - command should not have executed, so logger should be empty
252+
logger.LoggedObjects.ShouldBeEmpty();
253+
}
254+
183255
/// <summary>
184256
/// Creates the console application with logger.
185257
/// </summary>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Neolution.DotNet.Console.UnitTests.Fakes
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Hosting;
6+
using Neolution.DotNet.Console.Abstractions;
7+
using Neolution.DotNet.Console.UnitTests.Spies;
8+
9+
/// <summary>
10+
/// The command for the mixed verb - has properties but also custom parsing.
11+
/// </summary>
12+
/// <seealso cref="IDotNetConsoleCommand{TOptions}" />
13+
public class MixedCommand : IDotNetConsoleCommand<MixedOptions>
14+
{
15+
/// <summary>
16+
/// The logger
17+
/// </summary>
18+
private readonly IUnitTestLogger logger;
19+
20+
/// <summary>
21+
/// The environment
22+
/// </summary>
23+
private readonly IHostEnvironment environment;
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="MixedCommand" /> class.
27+
/// </summary>
28+
/// <param name="logger">The logger.</param>
29+
/// <param name="environment">The environment.</param>
30+
public MixedCommand(IUnitTestLogger logger, IHostEnvironment environment)
31+
{
32+
this.logger = logger;
33+
this.environment = environment;
34+
}
35+
36+
/// <inheritdoc />
37+
public async Task RunAsync(MixedOptions options, CancellationToken cancellationToken)
38+
{
39+
this.logger.Log("options", options);
40+
this.logger.Log("environment", this.environment);
41+
42+
// In real scenario, customers would manually parse additional args from Environment.GetCommandLineArgs()
43+
await Task.CompletedTask;
44+
}
45+
}
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace Neolution.DotNet.Console.UnitTests.Fakes
2+
{
3+
using CommandLine;
4+
using Neolution.DotNet.Console.Attributes;
5+
6+
/// <summary>
7+
/// The options stub for the <see cref="MixedCommand"/> - has properties but also allows unknown args
8+
/// </summary>
9+
[Verb("mixed")]
10+
[CustomArgumentParsing]
11+
public class MixedOptions
12+
{
13+
/// <summary>
14+
/// Gets or sets the name.
15+
/// </summary>
16+
[Option("name")]
17+
public string? Name { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the count.
21+
/// </summary>
22+
[Option("count")]
23+
public int Count { get; set; }
24+
}
25+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Neolution.DotNet.Console.UnitTests.Fakes
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Hosting;
6+
using Neolution.DotNet.Console.Abstractions;
7+
using Neolution.DotNet.Console.UnitTests.Spies;
8+
9+
/// <summary>
10+
/// The command for the process verb with custom argument parsing.
11+
/// </summary>
12+
/// <seealso cref="IDotNetConsoleCommand{TOptions}" />
13+
public class ProcessCommand : IDotNetConsoleCommand<ProcessOptions>
14+
{
15+
/// <summary>
16+
/// The logger
17+
/// </summary>
18+
private readonly IUnitTestLogger logger;
19+
20+
/// <summary>
21+
/// The environment
22+
/// </summary>
23+
private readonly IHostEnvironment environment;
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="ProcessCommand" /> class.
27+
/// </summary>
28+
/// <param name="logger">The logger.</param>
29+
/// <param name="environment">The environment.</param>
30+
public ProcessCommand(IUnitTestLogger logger, IHostEnvironment environment)
31+
{
32+
this.logger = logger;
33+
this.environment = environment;
34+
}
35+
36+
/// <inheritdoc />
37+
public async Task RunAsync(ProcessOptions options, CancellationToken cancellationToken)
38+
{
39+
this.logger.Log("options", options);
40+
this.logger.Log("environment", this.environment);
41+
42+
// In real scenario, customers would manually parse Environment.GetCommandLineArgs() here
43+
await Task.CompletedTask;
44+
}
45+
}
46+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Neolution.DotNet.Console.UnitTests.Fakes
2+
{
3+
using CommandLine;
4+
using Neolution.DotNet.Console.Attributes;
5+
6+
/// <summary>
7+
/// The options stub for the <see cref="ProcessCommand"/> - non-default verb with custom argument parsing
8+
/// </summary>
9+
[Verb("process")]
10+
[CustomArgumentParsing]
11+
public class ProcessOptions
12+
{
13+
// No properties defined - customers parse manually in the command
14+
}
15+
}

0 commit comments

Comments
 (0)