Using SpecFlow 3.0 with Rider

A while ago, I wrote about using SpecFlow with JetBrains IDE Rider. Recently, SpecFlow updated their version to 3.0 and it brings some different behaviour with it. After using it for a while, I really like the new flow.

The first change is that I need to install three packages, instead of two. SpecFlow and a unit testing framework (MSTest, NUnit or xUnit) still need to be installed. Additionally, SpecFlow.Tools.MsBuild.Generation needs to be installed as well.

The biggest benefit of the SpecFlow.Tools.MsBuild.Generation nuget is that I don’t need to set up a file writer anymore. I do need to update the .csproj file to include some configuration. The whole process is nicely documented on the SpecFlow site, but I’ll give the basics here.

First, right click on the test project and choose Edit > Edit MyProject.csproj option. Then just paste in the next bit of XML:

<Target Name="AfterUpdateFeatureFilesInProject">
    <!-- include any generated SpecFlow files in the compilation of the project if not included yet -->
    <ItemGroup>
        <Compile Include="**\*.feature.cs" Exclude="@(Compile)" />
    </ItemGroup>
</Target>

The old SpecFlow version can add configuration tags in the .csproj file: <Generator>SpecFlowSingleFileGenerator</Generator>. They can easily be replaced by doing a search and replace (ctrl+h in rider) and using this regex to find all of them: \n[ ]+SpecFlowSingleFileGenerator.

Save the .csproj file and just build the project. The .feature.cs files will be generated next to the .feature files. You can include them in the project, but the build server will update the generated files when it builds the project anyway. So you don’t need to include the .feature.cs files in the project anymore if you don’t want to.

Happy coding and keep those tests green!

Update: I got some feedback that it isn’t intuitive to find the Given/When/Then declarations for the .feature.cs file. All I need to do is run the tests. The method definitions appear in the test output window. If I add a new line, I just rerun the test. The test will be marked incomplete with the new method signature in the test output window. All that’s left to do is to copy and paste the signatures into the right file and flesh out the new method. Oh, and don’t forget to add Binding and Scope attribute on the class. I’ve forgotten that more times than I dare to admit.

Clean Architecture Applied – Review

During development, I already noticed some benefits and drawbacks.

Let’s start with the drawback: when I change a data structure, such as the Customer, there’s suddenly a few locations where this needs to be changed such as the tests and the repository. This isn’t unexpected, but I noticed there are a lot more changes than in other code. This can be because this experiment made me more aware or because the architecture accentuates this. In hindsight it is not such a big disadvantage, but more an observation of how this architecture brings these things to light.

A general observation about Clean Architecture is that this architecture is overkill for small project. It offers a lot of benefits, but I need to put in a lot of effort to get those benefits. Without all the interfaces and separate projects, I could be done much faster. Yet that would mean that it would be a lot less extendable. The extensibility can be added afterwards when I would need it. For example, I could put an IWorkRepository interface in place when I need to add additional data sources. It’s always a push and pull between YAGNI and not enough flexibility that make changing the design later difficult. The added interfaces and extension points did make me think twice about how I would go about some part of the code, which did benefit the end result.

The biggest advantage of this architecture is that it’s easy to extend. That’s not a big surprise as this is one of the pillars of Clean Architecture. Changing the single implementation of DinkToPdfDocumentGenerator into a wrapper for the internal functions that became FluidHtmlDocumentGenerator was effortless. There have been other places where this became apparent, but this was one of the most obvious.

An extension of the previous advantage is that the application is nicely compartmentalised. This allows me to focus on one problem at a time. It also forces me to keep my components small as they only focus on one part of the system. Small components are easy to reason with and the logic not only fits on a screen, but in my mind as well. I understand the problem space a lot better when it fits in my head and it helps me find a good solution for the problem at hand.

To be sure that components are truly separated, an inner circle component should never use the classes of the outer components. For example, in the TimeCampWorkRepository I have an object that allows me to deserialise the JSON structure I get back from the TimeCamp API. That object is not getting passed to the upper structure. I have a more generic object I map to so my inner components do not have to rely on the specific TimeCamp structure.

The WorkDay object is more than just a simplified view of the TimeCamp api. It’s a contract that describes what the component needs to do it’s work properly. If I use different repositories, for testing for example, all I have to do is make sure I return a good WorkDay object and my code will run.

Lastly, testing is not exclusive to Clean Architecture, but I can’t ignore their importance: tests really saved me a lot of time. While experimenting with certain alternatives or ways how to solve a problem, the tests showed me very quickly if I was going the right way. I use a mix of black box testing where I compare the result of a generation with a predetermined document and behavioural testing to check that I call some external services correctly.

The end

Clean Architecture is nothing new in my opinion, but it is a good set of principles to build an application upon. Don’t expect any ground breaking insights from the book, but a confirmation of what we, as a profession, are trying to work towards in terms of a good foundation. It offers a set of principles that outline how to structure an application so that it’s easy to build, maintained and extended.

I, for one, will be using this template as a starting point whenever I need to build a new application or when I need to work towards a better structure in legacy applications.

Clean Architecture Applied – Bringing it all together

Now all individual components are ready, it’s time to bring it all together in a working application.

To get all these parts together, I add the last project: the console that will run the whole thing. It wouldn’t be complete with the popular syntax of invoice auto. The Command Line Utils package allows me to convert the invoice auto args from the Main method into commands that can be executed. This package has a bit of a learning curve, but has a lot of options to customise the command line arguments. I’m not going to elaborate a lot on that, I liked the package and I’ll let my readers figure out how to use it best.

Lastly, there’s the setup of the objects in the Main function. It’s basic injection of the services with a little composition for the pdfGenerator that takes the htmlGenerator as input.

// in my console application
var dateProvider = new SystemDateProvider();
var customerRepository = new StaticCustomerRepository();
var timeCampWorkRepository = new TimeCampWorkRepository(configuration, customerRepository);
var htmlGenerator = new FluidHtmlDocumentGenerator();
var pdfGenerator = new DinkToPdfDocumentGenerator(htmlGenerator);
var generator = new InvoiceGenerator(dateProvider,
                                     customerRepository,
                                     timeCampWorkRepository,
                                     pdfGenerator);

Structuring the code

The ICustomerRepository interface should be placed in the use case layer (orange) and the implementation should be placed in the infrastructure layer (blue). Clean Architecture suggests grouping things that change with the same frequency and for the same reason. The infrastructure layer normally changes a lot more than the use case layer. I put the WorkRepository and the StaticCustomerRepository in separate projects. They change for different reasons at different times, so I should be able to build and deploy them at different times.

The document generators (FluidHtmlDocumentGenerator and DinkToPdfDocumentGenerator) are in a different project together. They both change when I need to update the document generation. Maybe not at the exact same time, but definitely for the same reason.

Above all else, the code should be easily testable. That is why I used all the interfaces. It’s easy to switch out an actual component for a fake one. The fake implementation allows me to control certain data. For example: I have a IDateProvider interface so I can control when Now actually is. If I would just use DateTime.Now, I could not simulate generating an invoice for a specific date. Now I just have two implementations: the SystemDateProvider in my actual application and the FakeDateProvider for in my tests.

// shared interface
public interface IDateProvider
{
  DateTime Now { get; }
}

// in my console application
internal class SystemDateProvider : IDateProvider
{
  public DateTime Now => DateTime.Now;
}

// in my tests
internal class FakeDateProvider : IDateProvider
{
  public FakeDateProvider(string now)
  {
    if (DateTime.TryParse(now, out var parsedNow))
    {
      Now = parsedNow;   
    }
  }
  public DateTime Now { get; }
}

The console project belongs in the infrastructure layer, together with the implementations of the interfaces. I put it in another layer in the image to indicate that the console brings all the other layers together.

The last blog in this series will talk about the pros and cons of this architecture.