DRN.Framework.Testing 0.3.0

The ID prefix of this package has been reserved for one of the owners of this package by NuGet.org. Prefix Reserved
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package DRN.Framework.Testing --version 0.3.0
NuGet\Install-Package DRN.Framework.Testing -Version 0.3.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DRN.Framework.Testing" Version="0.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add DRN.Framework.Testing --version 0.3.0
#r "nuget: DRN.Framework.Testing, 0.3.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install DRN.Framework.Testing as a Cake Addin
#addin nuget:?package=DRN.Framework.Testing&version=0.3.0

// Install DRN.Framework.Testing as a Cake Tool
#tool nuget:?package=DRN.Framework.Testing&version=0.3.0

master develop Quality Gate Status

Security Rating Maintainability Rating Reliability Rating Vulnerabilities Bugs Lines of Code Coverage

Introduction

DRN.Framework.Testing package provides practical, effective helpers such as resourceful data attributes and test context.

This package enables a new encouraging testing technique called as DTT(Duran's Testing Technique). With DTT, any developer can write clean and hassle-free unit and integration tests without complexity.

QuickStart: Basics

Here's a basic test demonstration to take your attention and get you started:

    [Theory]
    [DataInline]
    public void DataInlineDemonstration(TestContext context, IMockable autoInlinedDependency)
    {
        context.ServiceCollection.AddApplicationServices();
        //Context wraps service provider and automagically replaces actual dependencies with auto inlined dependencies
        var dependentService = context.GetRequiredService<DependentService>();
        
        autoInlinedDependency.Max.Returns(int.MaxValue); //dependency is already mocked by NSubstitute
        dependentService.Max.Should().Be(int.MaxValue); //That is all. It is clean and effective 
    }

Table of Contents

  • Introduction
  • TestContext
  • ContainerContext
  • WebApplicationContext
  • DataAttributes
  • DebugOnly Tests
  • Settings and Data Providers
  • Global Usings
  • Example Test Project
  • Test snippet
  • Testing guide and DTT approach

Testing models used in the QuickStart


public static class ApplicationModule //Can be defined in Application Layer or in Hosted App
{
    public static void AddApplicationServices(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddTransient<IMockable, ToBeRemovedService>(); //will be removed by test context because test method requested mocked interface
        serviceCollection.AddTransient<DependentService>(); //dependent service uses IMockable and Max property returns dependency's Max value
    }
}

public interface IMockable
{
    public int Max { get; }
}

public class ToBeRemovedService : IMockable
{
    public int Max { get; set; }
}

public class DependentService : IMockable
{
    private readonly IMockable _mockable;

    public DependentService(IMockable mockable)
    {
        _mockable = mockable;
    }

    public int Max => _mockable.Max;
}

QuickStart: Advanced Data Inline

  • DataInline will provide TestContext as first parameter.
  • Then it will provide inlined values.
  • Then it will provide auto inline missing values with AutoFixture.
  • AutoFixture will mock any interface requested with NSubstitute.
/// <param name="context"> Provided by DataInline even if it is not a compile time constant</param>
/// <param name="inlineData">Provided by DataInline</param>
/// <param name="autoInlinedData">DataInline will provide missing data with the help of AutoFixture</param>
/// <param name="autoInlinedMockable">DataInline will provide implementation mocked by NSubstitute</param>
[Theory]
[DataInline(99)]
public void TextContext_Should_Be_Created_From_TestContextData(TestContext context, int inlineData, Guid autoInlinedData, IMockable autoInlinedMockable)
{
    inlineData.Should().Be(99);
    autoInlinedData.Should().NotBeEmpty(); //guid generated by AutoFixture
    autoInlinedMockable.Max.Returns(int.MaxValue); //dependency mocked by NSubstitute

    context.ServiceCollection.AddApplicationServices(); //you can add services, modules defined in hosted app, application, infrastructure layer etc..
    var serviceProvider = context.BuildServiceProvider(); //appsettings.json added by convention. Context and service provider will be disposed by xunit
    serviceProvider.GetService<ToBeRemovedService>().Should().BeNull(); //Service provider behaviour demonstration

    var dependentService = serviceProvider.GetRequiredService<DependentService>();
    dependentService.Max.Should().Be(int.MaxValue);
}

TestContext

TestContext has following properties:

  • captures values provided to running test method, test method info and location.
  • provides ServiceCollection so that to be tested services and dependencies can be added before building ServiceProvider.
  • provides and implements lightweight ServiceProvider that contains default logging without any provider
    • ServiceProvider can provide services that depends on like ILogger<DefaultService>
    • logged data will not be leaked to anywhere since it has no logging provider.
  • provides ContainerContext
    • can start a postgres container, apply migrations for dbContexts derived from DrnContext and updates connection string configuration with a single line of code
  • provides WebApplicationContext
    • syncs TestContext service collection and service provider with provided application by WebApplicationFactory
  • provides IConfiguration and IAppSettings with SettingsProvider by using convention.
    • settings.json file can be found in the same folder with test
    • settings.json file can be found in the global Settings folder or Settings folder that stays in the test folder
    • Make sure file is copied to output directory
    • If no settings file is specified while calling BuildServiceProvider. appsettings.json file be searched by convention.
  • provides data file contents by using convention.
    • data file can be found in the same folder with test
    • data file can be found in the global Data folder or Data folder that stays in the test folder
    • Make sure file is copied to output directory
  • ServiceProvider provides utils provided with DRN.Framework.Utils' UtilsModule
  • BuildServiceProvider replaces dependencies that can be replaced with inlined interfaces.
  • ServiceProvider and TestContext will be disposed by xunit when test finishes

settings.json can be put in the same folder that test file belongs. This way providing and isolating test settings is much easier

    [Theory]
    [DataInline( "localhost")]
    public void TestContext_Should_Add_Settings_Json_To_Configuration(TestContext context, string value)
    {
        //settings.json file can be found in the same folder with test file, in the global Settings folder or Settings folder that stays in the same folder with test file
        context.GetRequiredService<IAppSettings>().GetRequiredSection("AllowedHosts").Value.Should().Be(value);
    }

data.txt can be put in the same folder that test file belongs. This way providing and isolating test data is much easier

    [Theory]
    [DataInline("data.txt", "Atatürk")]
    [DataInline("alternateData.txt", "Father of Turks")]
    public void TestContext_Should_Return_Test_Specific_Data(TestContext context, string dataPath, string data)
    {
        //data file can be found in the same folder with test file, in the global Data folder or Data folder that stays in the same folder with test file
        context.GetData(dataPath).Should().Be(data);
    }

ContainerContext

With ContainerContext and conventions you can easily write effective integration tests against your database dependencies

    [Theory]
    [DataInline]
    public async Task QAContext_Should_Add_Category(TestContext context)
    {
        context.ServiceCollection.AddSampleInfraServices();
        await context.ContainerContext.StartPostgresAndApplyMigrationsAsync();
        var qaContext = context.GetRequiredService<QAContext>();

        var category = new Category("dotnet8");
        qaContext.Categories.Add(category);
        await qaContext.SaveChangesAsync();
        category.Id.Should().BePositive();
    }
  • Application modules can be registered without any modification to TestContext
  • TestContext's ContainerContext
    • creates a postgresql container then scans TestContext's service collection for inherited DrnContexts.
    • Adds a connection strings to TestContext's configuration for each derived DrnContext according to convention.
  • TestContext acts as a ServiceProvider and when a service is requested it can build it from service collection with all dependencies.

WebApplicationContext

WebApplicationContext syncs TestContext service collection and service provider with provided application by WebApplicationFactory.

  • You can provide or override configurations and services to your program until you force WebApplicationFactory to build a Host such as creating HttpClient or requesting TestServer.
    [Theory]
    [DataInline]
    public async Task WebApplicationContext_Should_Provide_Configuration_To_Program(TestContext context)
    {
        var webApplication = context.WebApplicationContext.CreateWebApplication<Program>();
        await context.ContainerContext.StartPostgresAndApplyMigrationsAsync();

        var client = webApplication.CreateClient();
        var forecasts = await client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        forecasts.Should().NotBeNull();

        var appSettingsFromWebApplication = webApplication.Services.GetRequiredService<IAppSettings>();
        var connectionString = appSettingsFromWebApplication.GetRequiredConnectionString(nameof(QAContext));
        connectionString.Should().NotBeNull();

        var appSettingsFromTestContext = context.GetRequiredService<IAppSettings>();
        appSettingsFromWebApplication.Should().BeSameAs(appSettingsFromTestContext);//resolved from same service provider
    }

Data Attributes

DRN.Framework.Testing provides following data attributes that can provide data to tests:

  • DataInlineAttribute
  • DataMemberAttribute
  • DataSelfAttribute

Following design principle is used for these attributes

  • All attributes have data prefix to benefit from autocomplete
  • All data attributes automatically provide TestContext as first parameter if tests requires
  • All data attributes try to provide missing values with AutoFixture and NSubstitute
  • All data attributes will automatically override TestContext's service collection with provided NSubstitute interfaces
  • DataInline attribute works like xunit InlineData except they try to provide missing values with AutoFixture and NSubstitute
  • DataMember attribute works like xunit MemberData except they try to provide missing values with AutoFixture and NSubstitute
  • DateSelf attribute needs to be inherited by another class and should call AddRow method in constructor to provide data

Example usages for DataMember attribute

[Theory]
[DataMember(nameof(TestContextInlineMemberData))]
public void TestContextMember_Should_Inline_And_Auto_Generate_Missing_Test_Data(TestContext testContext,
    int inline, ComplexInline complexInline, Guid autoGenerate, IMockable mock)
{
    testContext.Should().NotBeNull();
    testContext.TestMethod.Name.Should().Be(nameof(TestContextMember_Should_Inline_And_Auto_Generate_Missing_Test_Data));
    inline.Should().BeGreaterThan(10);
    complexInline.Count.Should().BeLessThan(10);
    autoGenerate.Should().NotBeEmpty();
    mock.Max.Returns(75);
    mock.Max.Should().Be(75);
}

public static IEnumerable<object[]> TestContextInlineMemberData => new List<object[]>
{
    new object[] { 11, new ComplexInline(8) },
    new object[] { int.MaxValue, new ComplexInline(-1) }
};

Example usage for DataSelf attribute

public class DataSelfContextAttributeTests
{
    [Theory]
    [DataSelfContextTestData]
    public void TestContextClassData_Should_Inline_And_Auto_Generate_Missing_Test_Data(TestContext testContext,
        int inline, ComplexInline complexInline, Guid autoGenerate, IMockable mock)
    {
        testContext.Should().NotBeNull();
        testContext.TestMethod.Name.Should().Be(nameof(TestContextClassData_Should_Inline_And_Auto_Generate_Missing_Test_Data));
        inline.Should().BeGreaterThan(98);
        complexInline.Count.Should().BeLessThan(1001);
        autoGenerate.Should().NotBeEmpty();
        mock.Max.Returns(44);
        mock.Max.Should().Be(44);
    }
}

public class DataSelfContextTestData : DataSelfContextAttribute
{
    public DataSelfContextTestData1()
    {
        AddRow(99,new ComplexInline(100));
        AddRow(199,new ComplexInline(1000));
    }
}

Example usage for DataInline attribute

[Theory]
[DataInline(99)]
public void TextContext_Should_Be_Created_From_TestContextData(TestContext context, int inlineData, Guid autoInlinedData, IMockable autoInlinedMockable)
{
    inlineData.Should().Be(99);
    autoInlinedData.Should().NotBeEmpty(); //guid generated by AutoFixture
    autoInlinedMockable.Max.Returns(int.MaxValue); //dependency mocked by NSubstitute

    context.ServiceCollection.AddApplicationServices(); //you can add services, modules defined in hosted app, application, infrastructure layer etc..
    var serviceProvider = context.BuildServiceProvider(); //appsettings.json added by convention. Context and service provider will be disposed by xunit
    serviceProvider.GetService<ToBeRemovedService>().Should().BeNull(); //Service provider behaviour demonstration

    var dependentService = serviceProvider.GetRequiredService<DependentService>();
    dependentService.Max.Should().Be(int.MaxValue);
}

DebugOnly Tests

Following attributes can be used to run test only when the debugger is attached. These attributes does respect the attached debugger, not debug or release configuration.

  • FactDebuggerOnly
  • TheoryDebuggerOnly

Providers

SettingsProvider

SettingsProvider gets the settings from Settings folder. Settings file path is relative Settings folder. Settings folder must be created in the root of the test Project. Make sure the settings file is copied to output directory.

    [Fact]
    public void SettingsProvider_Should_Return_IAppSettings_Instance()
    {
        var appSettings = SettingsProvider.GetAppSettings();

        appSettings.GetRequiredSection("AllowedHosts").Value.Should().Be("*");
        appSettings.TryGetSection("Bar", out _).Should().BeTrue();
        appSettings.TryGetSection("Foo", out _).Should().BeFalse();
        appSettings.GetRequiredConnectionString("Foo").Should().Be("Bar");
        appSettings.TryGetConnectionString("Bar", out _).Should().BeFalse();
    }

    [Fact]
    public void SettingsProvider_Should_Return_IConfiguration_Instance()
    {
        var configuration = SettingsProvider.GetConfiguration("secondaryAppSettings");

        configuration.GetRequiredSection("AllowedHosts").Value.Should().Be("*");
        configuration.GetSection("Foo").Exists().Should().BeTrue();
        configuration.GetSection("Bar").Exists().Should().BeFalse();
        configuration.GetConnectionString("Bar").Should().Be("Foo");
    }

DataProvider

DataProvider gets the content of specified data file in the Data folder. Data file path is relative Data folder including file extension. Data folder must be created in the root of the test Project. Make sure the data file is copied to output directory.

    [Fact]
    public void DataProvider_Should_Return_Data_From_Test_File()
    {
        DataProvider.Get("Test.txt").Should().Be("Foo");
    }

Global Usings

Following global usings can be used in a Usings.cs file in test projects to reduce line of code in test files

global using Xunit;
global using AutoFixture;
global using AutoFixture.AutoNSubstitute;
global using AutoFixture.Xunit2;
global using DRN.Framework.Utils.Extensions;
global using DRN.Framework.SharedKernel;
global using DRN.Framework.Utils.Settings;
global using DRN.Framework.Utils.DependencyInjection;
global using DRN.Framework.Testing;
global using DRN.Framework.Testing.DataAttributes;
global using DRN.Framework.Testing.Providers;
global using DRN.Framework.Testing.TestAttributes;
global using DRN.Framework.Testing.Contexts;
global using FluentAssertions;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Configuration;
global using NSubstitute;
global using System.Reflection;
global using System.IO;
global using System.Linq;
global using System.Collections;

Example Test Project .csproj File

Don't forget to replace DRN.Framework.Testing project reference with its nuget package reference

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <IsPackable>false</IsPackable>
        <IsTestProject>true</IsTestProject>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\DRN.Framework.Testing\DRN.Framework.Testing.csproj"/>
    </ItemGroup>

    <ItemGroup>
        <None Update="Settings\defaultAppSettings.json">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
        <None Update="Data\Test.txt">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
        <None Update="Settings\secondaryAppSettings.json">
          <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
    </ItemGroup>

</Project>

Test snippet

dtt snippet for creating tests with a test context.

[Theory]
[DataInline]
public async Task $name$(TestContext context)
{
    $END$
}

Testing guide and DTT approach

DTT(Duran's Testing Technique) is developed upon following 2 idea to make testing natural part of the software development:

  • Writing a unit or integration test, providing settings and data to it should be easy, effective and encouraging as much as possible
  • A test should test actual usage as much as possible.

DTT with TestContext makes these ideas possible by

  • being aware of test data and location
  • effortlessly providing test data and settings
  • effortlessly providing service collection
  • effortlessly providing service provider
  • effortlessly validating service provider
  • effortlessly wiring external dependencies with Container Context
  • effortlessly wiring application with Application Context s With the help of test context, integration tests can be written easily with following styles.
  1. A data context attribute can provide NSubstituted interfaces and test context automatically replaces actual implementations with mocked interfaces and provides test data.
  2. Test containers can be used as actual dependencies instead of mocking them.
  3. With FactDebuggerOnly and TheoryDebuggerOnly attributes, cautiously written tests can use real databases and dependencies to debug production usage.

With DTT, software testing becomes natural part of the software development.


Semper Progredi: Always Progressive

Commit Info

Author: Duran Serkan KILIÇ
Date: 2024-04-23 20:47:24 +0300
Hash: dcde0b45114be0b91e9e3402f6f3cceca6b9ab3d

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.4.0-preview002 39 5/8/2024
0.4.0-preview001 64 5/5/2024
0.3.1-preview001 57 4/26/2024
0.3.0 65 4/23/2024
0.3.0-preview002 48 4/23/2024
0.3.0-preview001 65 4/23/2024
0.2.2-preview010 71 4/11/2024
0.2.2-preview009 77 3/18/2024
0.2.2-preview008 77 3/18/2024
0.2.2-preview007 71 3/16/2024
0.2.2-preview006 55 3/11/2024
0.2.2-preview005 66 3/10/2024
0.2.2-preview004 66 3/10/2024
0.2.2-preview003 84 1/22/2024
0.2.2-preview002 58 1/18/2024
0.2.2-preview001 60 1/14/2024
0.2.1 151 1/7/2024
0.2.0 118 12/31/2023
0.2.0-preview009 90 12/31/2023
0.2.0-preview008 95 12/30/2023
0.2.0-preview007 92 12/28/2023
0.2.0-preview006 86 12/27/2023
0.2.0-preview005 86 12/25/2023
0.2.0-preview004 82 12/23/2023
0.2.0-preview003 94 12/20/2023
0.2.0-preview002 86 12/19/2023
0.2.0-preview001 81 12/18/2023
0.1.0 157 11/26/2023
0.1.0-preview013 99 11/26/2023
0.1.0-preview012 93 11/20/2023
0.1.0-preview011 93 11/19/2023
0.1.0-preview010 102 10/30/2023
0.1.0-preview009 68 10/29/2023
0.1.0-preview008 89 10/27/2023
0.1.0-preview007 79 10/11/2023
0.1.0-preview006 83 10/9/2023
0.1.0-preview005 85 10/8/2023
0.1.0-preview004 82 10/8/2023
0.1.0-preview003 84 10/3/2023
0.1.0-preview002 97 10/3/2023
0.1.0-preview001 101 10/2/2023

Not every version includes changes, features or bug fixes. This project can increment version to keep consistency with other DRN.Framework projects.

## Version 0.3.0

My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals. This release is dedicated to 23 April National Sovereignty and Children's Day.

### Breaking Changes

* ContainerContext refactored and each Postgres and RabbitMQ usages refactored into PostgresContext and RabbitMQContext.
* WebApplicationContext renamed as ApplicationContext

### New Features

* PostgresContext and RabbitMQContext's now have global instances and isolated contexts
* ApplicationContext's LogToTestOutput method redirects application logs to test output when ITestOutputHelper is provided

### Bug Fixes

* Postgres container doesn't respect custom options

## Version 0.2.0

### Breaking Changes

* Data context and auto attributes unified into:
 * DataInlineAttribute
 * DataMemberAttribute
 * DataSelfAttribute
* Old data attributes removed.

### New Features

* TestContext exposes AddToConfiguration to add poco objects to configuration root with System.Text.Json.
* TestContext exposes BuildConfigurationRoot method.
* TestContext exposes GetConfigurationDebugView method.
* TestContext exposes ContainerContext and WebApplicationContext.
* FactDebuggerOnly and TheoryDebuggerOnly test attributes
* Following data attributes added:
 * DataInlineAttribute
 * DataMemberAttribute
 * DataSelfAttribute
* If TestContext is first parameter of the test method, data attributes will automatically detect and provide it.

## Version 0.1.0

### Breaking Changes

### New Features

* TestContext added
* FactDebuggerOnly and TheoryDebuggerOnly test attributes added
* Following data attributes added:
 * DataInlineAutoAttribute
 * DataInlineContextAttribute
 * DataMemberAutoAttribute
 * DataMemberContextAttribute
 * DataSelfAutoAttribute
 * DataSelfContextAttribute
* SettingsProvider added.
* DataProvider added.

### Bug Fixes

---
**Semper Progredi: Always Progressive**  
 
## Commit Info  
Author: Duran Serkan KILIÇ  
Date: 2024-04-23 20:47:24 +0300  
Hash: dcde0b45114be0b91e9e3402f6f3cceca6b9ab3d