Avatar

Blog (pg. 5)

  • Published on
    Often when I'm working on solutions I have a lot of projects that are all pretty much starting from the same place as another. For example, "unit test project targeting .net5, with FluentAssertions, Moq, Autofixture" - but also sometimes projects with a similar class structures too like a "repository that talks to MongoDB, with an ISomethingConfig", or a "Lambda with DI container". Of course I could create Visual Studio templates for these, but often templates fall out of date or I'd end up still needing to copy in some specific classes from a recently used project that I want to replicate. In a lot of cases I found myself copying and pasting and entire project folder, renaming the files accordingly then going through the csproj/cs files to replace the namespace and/or class names to target my "new" project. To automate this I wrote a Bash script, which might not be the most elegant or robust script in the world, but it does the job so I thought I'd share it here:
    
    # Script to create a new Visual Studio project folder from an existing one
    # Usage: ./csprojclone.sh Your.Namespaced.Source Your.Namespaced.Destinationn
    
    if [ $# -lt 2 ]
    	then
    		echo "Please pass source and destination namespace arguments"
    		return -1
    fi
    source=${1%%/}
    dest=${2%%/}
    
    if [[ ! -d $source ]]
    	then
    		echo "Source directory does not exist or isn't a folder - make sure you are in the correct working directory"
    		return -1
    fi
    
    if [[ -e $dest ]]
            then
                    echo "Destination folder already exists in the working directory"
                    return -1
    fi
    
    oldproject=${source%%.UnitTests}
    oldproject=${oldproject##*.}
    newproject=${dest%%.UnitTests}
    newproject=${newproject##*.}
    
    cp -r $source $dest
    rm -r $dest/bin/
    rm -r $dest/obj/
    find $dest -iname "*$source*" -exec rename -v "s/$source/$dest/" {} \;
    find $dest -iname "*$oldproject*" -exec rename -v "s/$oldproject/$newproject/" {} \;
    find $dest -type f -print0 | xargs -0 sed -i "s/$source/$dest/g"
    find $dest -type f -print0 | xargs -0 sed -i "s/$oldproject/$newproject/g"
    
    As per the script, the usage is to call the script from within your solution directory, passing in the name of the existing project folder and then a destination one. The script will clone the source, rename the files, search and replace the namespace change and update any classes that had the specific project name in. If you're using it on a unit tests project, it will strip of ".UnitTests" from the path - so if that's not your naming convention then feel free to edit those bits. Here's an example of how it might work: PWD = /path/to/SolutionFolder
        My.Namespace.UserRepository 
        My.Namespace.UserRepository/My.Namespace.UserRepository.csproj
        My.Namespace.UserRepository/MongoStuff/...
        My.Namespace.UserRepository/IUserRepositoryConfig.cs
        etc..
    
    
    . /path/to/script/csprojclone.sh My.Namespace.UserRepository My.Namespace.OrderRepository
    
    Will create:
        My.Namespace.OrderRepository
        My.Namespace.OrderRepository/My.Namespace.OrderRepository.csproj
        My.Namespace.OrderRepository/MongoStuff/...
        My.Namespace.OrderRepository/IOrderRepositoryConfig.cs
    
    With all namespaces also updated.
  • Published on
    Often C# classes will have several dependencies passed into the constructor and some (maybe all) of these will be mandatory for the class to function. In this case, ordinarily you'd add a null argument check in the ctor and if something is null throw an "ArgumentNullException". This is pretty boiler plate and is usually auto-generated code, but still it needs to be tested in your unit tests to assert that indeed all mandatory options have been checked (and conversely that all optional dependencies can indeed be null). This can be quite tedious and repetitive and later changing the signature of the ctor can result in many tests that requiring fixing up. To make these tests smaller and more concise I've come up with a new strategy using test cases and nullable mocks, as follows:
    
    // SomeClass.cs
    public class SomeClass
    {
    	private readonly IDependency1 _dependency1;
    	private readonly IDependency2 _dependency2;
    	private readonly IDependency3 _dependency3;
    
    	public SomeClass(IDependency1 dependency1, IDependency2 dependency2, IDependency3 dependency3)
    	{
    		_dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
    		_dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
    		_dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));
    	}
    }
    
    // SomeClass.tests.cs
    [TestFixture]
    public class SomeClassTests
    {
    	private Mock<IDependency1> _dependency1Mock;
    	private Mock<IDependency2> _dependency2Mock;
    	private Mock<IDependency3> _dependency3Mock;
    
    	[SetUp]
    	public void SetUp()
    	{
    		_dependency1Mock = new Mock<IDependency1>();
    		_dependency2Mock = new Mock<IDependency2>();
    		_dependency3Mock = new Mock<IDependency3>();
    	}
    
    	[TestCase("dependency1")]
    	[TestCase("dependency2")]
    	[TestCase("dependency3")]
    	public void Ctor_RequiredDependencyNull_ThrowsException(string dependency)
    	{
    		var setup = new Dictionary<string, Action>
    		{
    			{"dependency1", () => _dependency1Mock = null },
    			{"dependency2", () => _dependency2Mock = null },
    			{"dependency3", () => _dependency3Mock = null }
    		};
    		setup[dependency]();
    
    		Func<SomeClass> act = GetDefaultSut;
    
    		act.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be(dependency);
    	}
    
       private SomeClass GetDefaultSut()
    	{
    		return new SomeClass(_dependency1Mock?.Object, _dependency2Mock?.Object, _dependency3Mock?.Object);
    	}
    }
    
    My above example is for NUnit, using FluentAssertions and Moq but can be converted to your testing tools of choice. An even easier option, when all your dependencies are required is to use the "GuardClauseAssertion" from "AutoFixture.Idioms"
    
    private readonly Fixture _fixture;
    
    public SomeClassTest(){
         _fixture = new Fixture();
         _fixture.Customize(new AutoMoqCustomization());
    }
    
    [Test]
    public void Ctor_NullDependency_Throws()
    {
            new GuardClauseAssertion(_fixture)
                    .Verify(typeof(SomeClass).GetConstructors());
    }
    
  • Published on
    Before I begin with my recommendations it's probably worth me defining what I mean by "integration tests" as there is some ambiguity with this term. Given that I'm interested in running these tests as part of the development/build process and the purposes is to prove out that "this particular microservice behaves correctly against these external dependency contracts" - then I'm testing the intergration of the "units" of code within this system, not testing the integration of this system with the external dependencies. I find that writing these type of "integration tests" using SpecFlow is a great way to de-couple your behaviour from your code structure, making TDD more realistic and also meaning after a re-factor (which often results in re-factoring the unit tests) you can confirm there are no breaking changes. It also allows you to involve your QA/BA in the process by quantifying in plain English what scenario's you are catering for and how the system behaves for each. It's worth noting that SpecFlow can also be used to automate your "full integration tests", however that's a little more complex to setup as usually involved spinning up SQL servers, Kafka instances, mocked external APIs etc. and also is too slow to run those types of tests on build, whereas the integration tests I will demonstrate below you can quickly run on build like any other unit test. See the footnote on TDDF for a way to use the same set of tests with real dependencies too! To create a SpecFlow project for testing an API add an NUnit test project and install the SpecFlow.NUnit.Runners & Microsoft.AspNetCore.Mvc.Testing NuGet packages into that test project, add a reference to the Api project and then begin creating your tests. My recommendations to consider are below:
    • Create a "WebTestFixture" that inherits from "WebApplicationFactory<Startup>"
      • Where "Startup" is your API Statup class
      • Take constructor params to capture shared class instances from BoDi (the SpecFlow DI container) - e.g. your mocks
      • Override the "ConfigureWebHost" method and use "builder.ConfigureTestServices" to replace any "real" dependencies with mocks defined in the test project
      • Also register any other class instances that you want to share between BoDi and the .NET DI container
    • Create a folder structure that allows you consider the following genres of classes:
      • Infrastructure - e.g. SpecFlow hooks, Value Retrievers, Transformations etc. (basically the custom SpecFlow pipework)
      • TestDataProviders - with a subfolder for each high level dependency you are mocking (e.g. what would be a class library in the real implementation)
        • EachDataProvider - containing:
          • Mock Factory - create a class that will build your default mock for each interface (I prefer to use Moq)
          • StepDefinitions - All the SpecFlow step definitions for interacting with these mocks
        • Mocks Root Class - for easy injection of all your mocks into WebTestFixture and your step definitions
      • FolderPerController - the "tests" live in here so assuming your controllers align with a sensible functional grouping it makes sense to mirror that structure
        • Interactions - create a class which interacts with this controller via the "WebTestFixture.CreateClient()" HttpClient
        • Features - create a SpecFlow feature file per endpoint of the controller - in here create the scenario's this endpoint supports
        • Context - any classes that represent the data context of the controller itself (such as the data you will post, or the response from the API)
        • StepDefinitions - All the SpecFlow step definitions for interacting with this API controller and the assertions of the features

      This structure works well for me as it allows me to keep a separation of code specific to a controller or endpoint (making it easier to see what is involved with which moving part) from each other, but also allows code re-use of steps which are for contriving data in your mocked repositories, with a clear separation again which would match the structure of your class libraries of your project. And of course, once you have defined the features/steps/data required to interact with all mocks and all controllers/endpoints - you can create a high level folder of features that interact across multiple of these, if you have such scenarios to assert. For a strategy of preparing your test data that can be used both in-memory and against a "real" datastore, see TDDF
  • Published on
    When you have multiple downstream dependencies that are accessed via HttpClient in .NET then you want a simple way of logging all the error responses that are received from those calls. Often your application will react to the non-successful response by logging it's own error, but this can sometimes miss the detail of what actually went wrong downstream. An easy to way to capture all the non-successful outbound calls your application makes is to inject a custom delegating handler into all instances of HttpClient (via DI) which can inspect the return code and call out to your logger if necessary: LoggingMessageHandler.cs:
    
    public class LoggingMessageHandler : DelegatingHandler
    {
    	private readonly IExceptionLogger _exceptionLogger;
    
    	public LoggingMessageHandler(IExceptionLogger exceptionLogger)
    	{
    		_exceptionLogger = exceptionLogger ?? throw new ArgumentNullException(nameof(exceptionLogger));
    	}
    
    	protected override async Task<HttpResponseMessage> SendAsync(
    		HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    	{
    		var response = await base.SendAsync(request, cancellationToken);
    		if (!response.IsSuccessStatusCode)
    		{
    			var responseContent = await response.Content.ReadAsStringAsync();
    			await _exceptionLogger.LogExceptionAsync(new HttpRequestException(
    				$"Api call to {request.RequestUri} failed with status code {(int)response.StatusCode}. Response content: {responseContent}"));
    		}
    		return response;
    	}
    }
    
    Your implementation of "IExceptionLogger" may vary, but this is your opportunity to write something to your logs/DB etc. To set this delegating handler on your HttpClient, setup the Microsoft DI container as follows:
    
    services.AddHttpClient<ISomeClient, SomeClient>().AddHttpMessageHandler<LoggingMessageHandler>()
    
    Since this class and custom logic will typically live in your composition root, I'd recommend passing the builder delegate down to any class libraries you are building (where you choose to define the ServiceCollectionExtensions inside the class library). For example: Startup.cs:
    
    services.AddMyCustomLibrary(builder => builder.AddHttpMessageHandler<LoggingMessageHandler>());
    
    CustomLibrary/ServiceCollectionExtensions.cs:
    
    public static void AddMyCustomLibrary(this IServiceCollection services, Action<IHttpClientBuilder> clientBuilderDelegate = null)
    {
    	var someInnerClientBuilder = services.AddHttpClient<ISomeInnerClient, SomeInnerClient>();
    	clientBuilderDelegate?.Invoke(someInnerClientBuilder);
    }
    
  • Published on
    It's pretty common practice in .NET Core to take a dependency on HttpClient in your constructor and using the built-in DI container extension to register this. When it comes to unit testing it can always be a bit fiddly when you depend on a concrete class rather than an interface. After solving this problem several times when it comes to HttpClient based unit tests I've create a simple TestHttpClient and TestHttpClientBuilder to simplify the process:
    
    public class TestHttpClientBuilder
    {
            private readonly HttpResponseMessage _stubHttpResponseMessage = new(HttpStatusCode.OK);
            private Exception? _exception;
     
            public TestHttpClientBuilder WithStatusCode(HttpStatusCode statusCode)
            {
                _stubHttpResponseMessage.StatusCode = statusCode;
                return this;
            }
     
            public TestHttpClientBuilder WithJsonContent<T>(T expectedResponseObject)
            {
                _stubHttpResponseMessage.Content = new StringContent(JsonConvert.SerializeObject(expectedResponseObject), Encoding.UTF8, "application/json");
                return this;
            }
     
            public TestHttpClientBuilder WithException(Exception ex)
            {
                _exception = ex;
                return this;
            }
     
            public TestHttpClient Build()
            {
                return new TestHttpClient(
                    _exception != null ? 
                        new FakeHttpMessageHandler(_exception) : 
                        new FakeHttpMessageHandler(_stubHttpResponseMessage));
            }
     
            public class TestHttpClient : HttpClient
            {
                private readonly FakeHttpMessageHandler _httpMessageHandler;
     
                internal TestHttpClient(FakeHttpMessageHandler httpMessageHandler) : base(httpMessageHandler)
                {
                    _httpMessageHandler = httpMessageHandler;
                    BaseAddress = new Uri("http://localhost.com");
                }
     
                public IReadOnlyList<HttpRequestMessage> CapturedRequests => _httpMessageHandler.CapturedRequests;
            }
    }
     
    public class FakeHttpMessageHandler : HttpMessageHandler
    {
            private readonly Exception? _exception;
            private readonly HttpResponseMessage _response = new();
            private readonly List<HttpRequestMessage> _capturedRequests = new List<HttpRequestMessage>();
     
            public FakeHttpMessageHandler(Exception exception)
            {
                _exception = exception;
            }
     
            public FakeHttpMessageHandler(HttpResponseMessage response)
            {
                _response = response;
            }
     
            public IReadOnlyList<HttpRequestMessage> CapturedRequests => _capturedRequests;
     
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                CancellationToken cancellationToken)
            {
                _capturedRequests.Add(request);
     
                if (_exception != null)
                {
                    throw _exception;
                }
     
                return Task.FromResult(_response);
            }
    }
    
    Given this code is available to your unit tests, you can now use the builder when instantiating the SUT and use the builder methods to configure the possible responses and/or inspect the captured requests to test your outbound calls. e.g.
    
    public class UnitTestClass
    {
    	private TestHttpClientBuilder? _testHttpClientBuilder;
    	private Lazy<TestHttpClientBuilder.TestHttpClient>? _testHttpClient;
    
    	public void SetUp()
    	{
    		_testHttpClientBuilder = new TestHttpClientBuilder()
    			.WithStatusCode(HttpStatusCode.OK)
    			.WithJsonContent(new MyDataType()); // use AutoFixture, test data builder etc. to create a default response
    
    		_testHttpClient = new Lazy<TestHttpClientBuilder.TestHttpClient>(() => _testHttpClientBuilder.Build());
    	}
    
    	// Now create tests on the SUT using "_testHttpClient.Value" for the HttpClient ctor argument.
    	
    	// Make assertions based on changing the response status code / content, or by inspecting "_testHttpClient.Value.CapturedRequests"
    }