Skip to content

TestConfig#

TestConfig#

The TestConfig is used to control what is logged to output during a test run.

The default value will output the console buffer, both Out and Error, on error.

Set TestConfig.Default to provide a different default. If you're using a test framework that doesn't provide for a way to set the default before tests are run, looking at you XUnit, then implement an IDefaultTestConfig as shown below

public class TestConfig
{
    /// <summary>
    /// Default scans loaded assemblies for <see cref="IDefaultTestConfig"/>
    /// and stores the config with the lowest <see cref="Priority"/>
    /// </summary>
    public static TestConfig Default { get; set; }

    /// <summary>Nothing will be printed and errors will be captured</summary>
    public static TestConfig Silent { get; set; }

    /// <summary>
    /// Configuration to be used when no exception has 
    /// escaped <see cref="AppRunner.Run"/><br/>
    /// Default: prints nothing
    /// </summary>
    public OnSuccessConfig OnSuccess { get; set; }

    /// <summary>
    /// Configuration to be used when no exception has 
    /// escaped <see cref="AppRunner.Run"/><br/>
    /// Default: prints <see cref="PrintConfig.ConsoleOutput"/>
    /// </summary>
    public OnErrorConfig OnError { get; set; }

    /// <summary>When true, CommandDotNet logs will output to logLine</summary>
    public bool PrintCommandDotNetLogs { get; set; }

    /// <summary>
    /// To identify the <see cref="TestConfig"/> in case 
    /// the expected config was not used.<br/>
    /// Will be auto-populated when created from 
    /// <seealso cref="IDefaultTestConfig"/>
    /// </summary>
    public string Source { get; set; }

    /// <summary>
    /// When multiple <see cref="IDefaultTestConfig"/>s are found,
    /// the <see cref="TestConfig"/> with the lowest priority will be used.<br/>
    /// This property is only needed when providing the default via 
    /// <see cref="IDefaultTestConfig"/><br/>
    /// Set <see cref="Default"/> directly to avoid use of this property.<br/>
    /// Create and .gitignore a <see cref="IDefaultTestConfig"/> 
    /// with short.MinValue for verbose local logging and quite CI logging.<br/>
    /// </summary>
    public short Priority { get; set; } = short.MaxValue;

    /// <summary>
    /// Returns a clone of the TestConfig after applying 
    /// the <see cref="alter"/> action
    /// </summary>
    public TestConfig Where(Action<TestConfig> alter){}

    public class OnSuccessConfig
    {
        public PrintConfig Print { get; set; }
    }

    public class OnErrorConfig
    {
        /// <summary>
        /// When true, errors escaping <see cref="AppRunner.Run"/> will be
        /// captured in <see cref="AppRunnerResult"/> and 
        /// <see cref="AppRunnerResult.ExitCode"/> will be set to 1. 
        /// This mimics how the shell will process it.
        /// </summary>
        public bool CaptureAndReturnResult { get; set; }
        public PrintConfig Print { get; set; }
    }

    public class PrintConfig
    {
        /// <summary>When true, all options will be printed</summary>
        public bool All
        {
            get => AppConfig && CommandContext 
                    && ConsoleOutput && ParseReport;
            set => AppConfig = CommandContext 
                    = ConsoleOutput = ParseReport = value;
        }

        /// <summary>Print the <see cref="AppConfig"/></summary>
        public bool AppConfig { get; set; }

        /// <summary>Print the <see cref="CommandContext"/></summary>
        public bool CommandContext { get; set; }

        /// <summary>Print the <see cref="TestConsole.AllText"/></summary>
        public bool ConsoleOutput { get; set; }

        /// <summary>
        /// Print the output of <see cref="ParseReporter.Report"/> 
        /// to see how values are assigned to arguments
        /// </summary>
        public bool ParseReport { get; set; }
    }
}

IDefaultTestConfig#

Implement an IDefaultTestConfig when you need the config to be auto-discovered.

TestConfig.Default will lazy load a default if one is not provided. The lazy load will scan all loaded assemblies for implementations of IDefaultTestConfig. The resulting TestConfigs will be loaded and the one with the lowest priority will be selected.

/// <summary>Implement this to provide a TestConfig for tests</summary>
public interface IDefaultTestConfig
{
    /// <summary>The TestConfig to use as a default</summary>
    TestConfig Default { get; }
}

This supports the scenario where a dev keeps a DevDefaultTestConfig in the local directory to log All on an error and ConsoleOutput on success. The file can be added to .gitignore, just like an appSettings.env.local file. This is possible with the new project files that only require the file to be present.

public class DevDefaultTestConfig : IDefaultTestConfig
{
    public TestConfig Default => new TestConfig
    {
        Priority = short.MinValue,
        PrintCommandDotNetLogs = true,
        OnSuccess = {Print = {ConsoleOutput = true}},
        OnError = {Print = {All = true}}
    };
}

This approach was chosen because it was simple to implement, required no additional json parsing libraries and works well with the maintainers work flow. If you need json support, start with this config and the serializer of your choice. We may add another package with this eventually.

public class JsonDefaultTestConfig : IDefaultTestConfig
{
    const string JsonFile = "CommandDotNet.TestConfig.json";
    public TestConfig Default 
    {
        get
        {
            if (File.Exists(JsonFile))
            {
                var json = File.ReadAllText(JsonFile);
                var testConfig = JsonSerializer.Deserialize<TestConfig>(json);
                testConfig.Source = Path.GetFullPath(JsonFile);
                if (!testConfig.Priority.HasValue)
                {
                    testConfig.Priority = 0;
                }
                return testConfig;
            }

            return null;
        }
    }
}

Example of all output#

Here is an example of what you'll see when PrintCommandDotNetLogs=true and On___.Print.All=true.

ConsoleOutput and ParseReport will the most useful in regular scenarios.

The CommandContext and AppConfig sections are likely more helpful when debugging middleware or other extensibility points or failures following an update of the framework.

I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: <CaptureState>b__1 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: TokenizeInputMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: CreateRootCommand 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: ParseInputMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: AssembleInvocationPipelineMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: CheckIfShouldShowHelp 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: BindValues 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: ResolveCommandClassInstances 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: Middleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: InjectTestCaptures 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > begin: invoke middleware: InvokeInvocationPipelineMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: InvokeInvocationPipelineMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: InjectTestCaptures 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: Middleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: ResolveCommandClassInstances 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: BindValues 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: CheckIfShouldShowHelp 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: AssembleInvocationPipelineMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: ParseInputMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: CreateRootCommand 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: TokenizeInputMiddleware 
I CommandDotNet.Execution.ExecutionMiddlewareExtensions > end: invoke middleware: <CaptureState>b__1 

Console output <begin> ------------------------------
<no output>
Console output <end> ------------------------------


CommandContext:
  RootCommand:Command Command:App  (CommandDotNet.Tests.CommandDotNet.FluentValidation.ModelValidationTests+App)
  ShowHelpOnExit:False
  Original.Args:Save 1 john john@doe.com
  Tokens:Save 1 john john@doe.com
  ParseResult:ParseResult:
    TargetCommand:Command Command:Save  (CommandDotNet.Tests.CommandDotNet.FluentValidation.ModelValidationTests+App.Save)
    RemainingOperands:
    SeparatedArguments:
    ParseError:
  InvocationPipeline:InvocationPipeline:
    TargetCommand:InvocationStep:
      Command=Save
      Invocation=CommandDotNet.Tests.CommandDotNet.FluentValidation.ModelValidationTests+App.Save
      Instance=CommandDotNet.Tests.CommandDotNet.FluentValidation.ModelValidationTests+App



Parse report <begin> ------------------------------
command: Save

arguments:

  Id <Number>
    value: 1
    inputs: 1
    default:

  Name <Text>
    value: john
    inputs: john
    default:

  Email <Text>
    value: john@doe.com
    inputs: john@doe.com
    default:
Parse report <end> ------------------------------


AppRunner<App>:
  AppConfig:
    AppSettings:
      ArgumentTypeDescriptors: ArgumentTypeDescriptors:
        ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.BoolTypeDescriptor
        ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.EnumTypeDescriptor
        ErrorReportingDescriptor > DelegatedTypeDescriptor<String>: 'Text'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Password>: 'Text'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Char>: 'Character'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Int64>: 'Number'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Int32>: 'Number'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Int16>: 'Number'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Decimal>: 'Decimal'
        ErrorReportingDescriptor > DelegatedTypeDescriptor<Double>: 'Double'
        ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.ComponentModelTypeDescriptor
        ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.StringCtorTypeDescriptor
      BooleanMode: Implicit
      DefaultArgumentMode: Operand
      DefaultArgumentSeparatorStrategy: PassThru
      DisableDirectives: False
      GuaranteeOperandOrderInArgumentModels: False
      Help: AppHelpSettings:
        ExpandArgumentsInUsage: False
        PrintHelpOption: False
        TextStyle: Detailed
        UsageAppName: 
        UsageAppNameStyle: Adaptive
      IgnoreUnexpectedOperands: False
      LongNameAlwaysDefaultsToSymbolName: False
    DependencyResolver: CommandDotNet.TestTools.TestDependencyResolver
    HelpProvider: CommandDotNet.Help.HelpTextProvider
    TokenTransformations:
      expand-clubbed-flags(2147483647)
      split-option-assignments(2147483647)
    MiddlewarePipeline:
      HelpMiddleware.PrintHelp
      <>c__DisplayClass0_0.<CaptureState>b__1
      TokenizerPipeline.TokenizeInputMiddleware
      ClassModelingMiddleware.CreateRootCommand
      CommandParser.ParseInputMiddleware
      ClassModelingMiddleware.AssembleInvocationPipelineMiddleware
      HelpMiddleware.CheckIfShouldShowHelp
      BindValuesMiddleware.BindValues
      ResolveCommandClassesMiddleware.ResolveCommandClassInstances
      FluentValidationMiddleware.Middleware
      AppRunnerTestExtensions.InjectTestCaptures
      ClassModelingMiddleware.InvokeInvocationPipelineMiddleware
    ParameterResolvers:
      CommandDotNet.CommandContext
      CommandDotNet.Rendering.IConsole
      System.Threading.CancellationToken