<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jon Dodd</title>
    <description>The latest articles on DEV Community by Jon Dodd (@jdinnovensa).</description>
    <link>https://dev.to/jdinnovensa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F629994%2Fc1bc55dc-d542-47a6-8a7e-2d2ece186f86.png</url>
      <title>DEV Community: Jon Dodd</title>
      <link>https://dev.to/jdinnovensa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jdinnovensa"/>
    <language>en</language>
    <item>
      <title>AI in Software Development: A Mirror, Not a Magic Wand</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Fri, 13 Mar 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/ai-in-software-development-a-mirror-not-a-magic-wand-gge</link>
      <guid>https://dev.to/jdinnovensa/ai-in-software-development-a-mirror-not-a-magic-wand-gge</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;“AI’s inflection point has arrived.”&lt;/strong&gt; This statement reflects how deeply artificial intelligence is embedding itself into our daily lives. For software developers, that reality is unavoidable. Whether you’re using GitHub Copilot for small productivity gains or experimenting with more advanced agentic workflows, AI-assisted development is here to stay — and learning to use it well is quickly becoming essential.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"AI’s inflection point has arrived." — &lt;strong&gt;Jensen Huang&lt;/strong&gt;, NVIDIA CEO&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yet scratch beneath the surface and you’ll find sharply mixed opinions. Some developers praise AI for dramatic productivity gains. Others argue it introduces subtle bugs, encourages shallow understanding, and accelerates the creation of tomorrow’s legacy code.&lt;/p&gt;

&lt;p&gt;I believe both sides are right.&lt;/p&gt;

&lt;h1&gt;
  
  
  Two Core Premises to Keep in Mind
&lt;/h1&gt;

&lt;p&gt;How can two seemingly contradictory viewpoints both be true? The answer lies in two simple premises.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. AI does not change the need for good engineering discipline
&lt;/h2&gt;

&lt;p&gt;AI can assist you, but it does not replace understanding. If you already follow best practices and think carefully about design, correctness, and performance, AI will help you move faster. If you don’t, it will happily help you make things worse — just more quickly.&lt;/p&gt;

&lt;p&gt;AI shortens feedback loops. Hours of work can now become minutes. That speed is powerful, but it is neutral. Whether it improves or degrades a codebase depends entirely on the developer using it.&lt;/p&gt;

&lt;p&gt;Which leads directly to the second premise.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. AI amplifies whatever habits you already have
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Careful developers → faster, still careful&lt;/li&gt;
&lt;li&gt;Sloppy developers → faster, more sloppy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you stop thinking of AI as a replacement for developers and instead see it as an amplifier of existing workflows, the current state of AI-assisted development starts to make much more sense.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Multiplier Effect in Practice
&lt;/h1&gt;

&lt;p&gt;To see how this plays out, let’s look at what happens when AI meets different development styles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The “Sloppy Dev” Scenario&lt;/strong&gt;&lt;br&gt;
What does “faster, more sloppy” look like in practice?&lt;/p&gt;

&lt;p&gt;Imagine a rushed or inexperienced developer who blindly accepts a large block of AI-generated code without fully understanding it — ignoring algorithmic complexity, edge cases, or even hallucinated dependencies. AI allows them to generate technical debt at 10× speed.&lt;/p&gt;

&lt;p&gt;For example, a developer needs to process a list of users and find duplicates based on email address. They prompt an AI assistant and immediately accept the following C# code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feshfdate8hqec6p39sgs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feshfdate8hqec6p39sgs.jpg" alt=" " width="632" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code “works” locally with ten test users. The developer merges it and moves on.&lt;/p&gt;

&lt;p&gt;In production, with 100,000 users, this nested loop (O(n²)) combined with repeated .Contains() calls severely degrades performance. The problem wasn’t AI — it was the lack of review and understanding. AI simply accelerated the damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The “Careful Dev” Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now contrast that with a disciplined developer.&lt;/p&gt;

&lt;p&gt;A senior engineer might use AI to scaffold a REST API, generate boilerplate, or sketch an initial solution. But they treat the AI like a junior pair programmer — not an authority. They review the output, question it, and improve it before shipping.&lt;/p&gt;

&lt;p&gt;Using the same duplicate-finding problem, the careful developer immediately spots the performance issue and re-prompts the AI.&lt;/p&gt;

&lt;p&gt;The AI enthusiastically apologizes and offers this "optimized" solution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jpjcpl26yjyhfx3nkhp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jpjcpl26yjyhfx3nkhp.jpg" alt=" " width="708" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sloppy developer would have merged this, thrilled that it runs faster. But the careful developer goes a step further: they actually read the code to ensure logical soundness. In doing so, they spot a subtle, dangerous flaw.&lt;/p&gt;

&lt;p&gt;This code misses the first record. HashSet.Add returns true for the first instance of an email, so it bypasses the if block. If there are two users with the same email, this function only returns the second one. The AI generated code that looks confidently correct but contains a subtle logical flaw caused by an implicit assumption.&lt;/p&gt;

&lt;p&gt;Because the careful developer understands their tools, they discard the AI's flawed logic and write a definitive, bug-free, and idiomatic C# LINQ query instead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq7ddaac270r0h9x3tap.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq7ddaac270r0h9x3tap.jpg" alt=" " width="701" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The careful developer used AI to get the initial momentum, but they applied their own engineering rigor to ensure the solution was performant, clean, and logically perfect.&lt;/p&gt;

&lt;p&gt;The important point isn’t that the AI made a mistake — it’s that only a disciplined developer noticed.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Engineering Discipline Checklist
&lt;/h1&gt;

&lt;p&gt;To consistently benefit from AI rather than fight it, a few foundational habits matter more than ever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design incrementally&lt;/strong&gt;&lt;br&gt;
AI struggles with whole systems but excels at focused tasks. Architect the system yourself, then ask AI to help with specific components — a repository, a mapping function, a DI configuration. Smaller prompts produce cleaner results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test as you go&lt;/strong&gt;&lt;br&gt;
When code is generated at high speed, guardrails are essential. AI can produce logic that compiles perfectly but fails subtly. Strong unit tests (xUnit, NUnit, etc.) catch these issues early and cheaply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review what you merge&lt;/strong&gt;&lt;br&gt;
Code review is now critical, even for solo developers. You may not have written every line, but you still own every line you commit. Treat AI-generated code exactly like code from a human teammate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stop when the problem is solved&lt;/strong&gt;&lt;br&gt;
AI will happily keep elaborating long after a solution is good enough. Knowing when to stop refining is a human skill — and an underrated one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;If you take nothing else away, remember these two points:&lt;/p&gt;

&lt;p&gt;AI does not remove the need for good engineering discipline&lt;br&gt;
AI amplifies whatever habits you already have&lt;br&gt;
Software development is evolving rapidly, but responsibility hasn’t moved. The tools are getting smarter — but they still reflect the discipline, or lack of it, of the developer holding the keyboard.&lt;/p&gt;




&lt;p&gt;If you’re interested in how these ideas translate into real projects, I’ve written more about how we approach &lt;a href="https://www.innovensa.co.uk/services/dotnet-development" rel="noopener noreferrer"&gt;maintainable .NET application development&lt;/a&gt; — focusing on clean code, sensible design, and long-term maintainability rather than chasing hype.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Inherited a .NET Nightmare? 5 Strategies to Tame Legacy Code</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Thu, 05 Mar 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/inherited-a-net-nightmare-5-strategies-to-tame-legacy-code-4c0d</link>
      <guid>https://dev.to/jdinnovensa/inherited-a-net-nightmare-5-strategies-to-tame-legacy-code-4c0d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a developer, it is almost inevitable that you’ll eventually work on maintaining or modernising an existing codebase. Rarely is this straightforward; the challenge can feel overwhelming, especially if the core logic is a tangled mess and a classic ‘big ball of mud’. This lack of clarity decreases your confidence and increases the risk of the system breaking in unexpected places whenever you make changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is legacy .NET code?
&lt;/h2&gt;

&lt;p&gt;A common misconception is that legacy code must be old code. While age can be a factor, a system might use modern frameworks but still be poorly organised and patched together to meet a deadline. While many factors define legacy code, we’ll focus on two of the biggest ones today.&lt;/p&gt;

&lt;p&gt;First, if the code has &lt;strong&gt;no tests&lt;/strong&gt;, it is legacy. Michael Feathers^1^ famously defined legacy code as “code without tests” because the absence of a safety net makes changing the system risky and expensive.&lt;/p&gt;

&lt;p&gt;Second, if the code and infrastructure have &lt;strong&gt;no automated deployments&lt;/strong&gt;, it is legacy. Modern software hosted on cloud infrastructure should be readily deployable via automation. If a release requires intricate manual interventions from a developer's workstation, the risk of redeploying the app is simply too high.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should I rebuild my .NET application?
&lt;/h2&gt;

&lt;p&gt;When inheriting a large .NET monolith, scrapping it and starting from a clean slate seems incredibly appealing. It’s a tempting thought, but the "Big Bang" rewrite is almost always a trap.&lt;/p&gt;

&lt;p&gt;These massive rewrites consistently take longer than estimated because developers often base their timelines on a superficial analysis of the screens or APIs. This approach frequently misses undocumented business rules buried deep in the codebase, which sometimes aren't discovered until bugs are raised in production. Furthermore, new development grinds to a halt until the rewrite is complete; spending two years adopting new tech without innovating can cause a business to lose out financially and competitively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59hm7g4heesvlxq5bnpe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59hm7g4heesvlxq5bnpe.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What can I do to modernise my .NET legacy application?
&lt;/h2&gt;

&lt;p&gt;If we rule out the big rewrite, how do we move forward? First, you need to adopt a specific mindset: Adopt a cautious, deliberate approach.&lt;/p&gt;

&lt;p&gt;With large, tangled codebases, rushing in can introduce unknown bugs to other parts of the system. You must tread cautiously. For instance, you might spot a guard clause at the beginning of a method that seems to serve no purpose and delete it, only for issues to emerge in the next release. This perfectly illustrates &lt;a href="https://fs.blog/chestertons-fence/" rel="noopener noreferrer"&gt;Chesterton’s fence ↗&lt;/a&gt;: just because a piece of legacy code shows no obvious purpose to you, it doesn’t mean it isn’t important.&lt;/p&gt;

&lt;h2&gt;
  
  
  What strategies can I use to modernise my .NET codebase?
&lt;/h2&gt;

&lt;p&gt;Once you have the right mindset, you can start applying tactical solutions. Here are five proven strategies to regain control of your .NET application.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Characterisation Testing: Build Your Safety Net
&lt;/h3&gt;

&lt;p&gt;One of Michael Feathers’ most critical strategies is locking-in the system's existing behaviour, even if it is buggy. Characterisation tests assert what the system actually does right now, not what it should do. The goal isn't to fix the logic immediately, but to create a testable benchmark. Once you have this safety net proving how the system behaves, you can refactor the messy code with confidence that you haven't lost an undocumented business rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0xwkagcjv0unfqbwrev.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0xwkagcjv0unfqbwrev.jpg" alt=" " width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Sprout and Wrap
&lt;/h3&gt;

&lt;p&gt;When strict time constraints prevent you from refactoring the surrounding mess, Feathers suggests two techniques to safely add functionality. The Sprout technique involves writing your new feature as a completely new, fully tested method or class, and then simply inserting a call to it from within the legacy code. Alternatively, the Wrap technique has you rename the old method, create a new method with the original name, and have that new method execute both the old logic and your new additions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Boy Scout Rule
&lt;/h3&gt;

&lt;p&gt;Popularised by Robert C. Martin (“Uncle Bob”), &lt;a href="https://www.informit.com/articles/article.aspx?p=1235624&amp;amp;seqNum=6" rel="noopener noreferrer"&gt;the Boy Scout rule ↗&lt;/a&gt; is simple: “Always leave the code cleaner than you found it”. If you are updating a method, take a moment to rename vague variables to provide clear meaning, or remove unused parameters. These small improvements compound over time, leading to vastly better maintainability. Equally important is applying this rule to your test suite; cleaning up your tests increases your confidence in the whole system.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Strangler Fig Pattern
&lt;/h3&gt;

&lt;p&gt;If your goal is to modernise a monolith into a modular architecture, the safest route is the Strangler Fig Pattern (&lt;a href="https://martinfowler.com/bliki/StranglerFigApplication.html" rel="noopener noreferrer"&gt;a concept championed by Martin Fowler ↗&lt;/a&gt;). Imagine you have a massive, ten-year-old ASP.NET Web Forms app, and your operations team desperately needs a new mobile-friendly "Driver Dispatch" module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5za7i0b7bo8ikpwp4uk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5za7i0b7bo8ikpwp4uk.png" alt=" " width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of rewriting the whole logistics system, you place a routing proxy (a "Traffic Cop") in front of the legacy app, initially routing 100% of traffic to the old system. Next, you build the new "Driver Dispatch" feature inside a cleanly architected and testable .NET application. If your internal team is bogged down maintaining the old monolith, this is often a great time to leverage expert .NET application development services to ensure the new architecture is built right from day one. Finally, you update the proxy rules so that billing clicks go to the old system, but dispatch clicks route seamlessly to the new app. Users utilise both systems simultaneously without noticing, allowing you to slowly extract modules over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Automate the "Magic" Deployments
&lt;/h3&gt;

&lt;p&gt;If your system requires a developer to manually execute SQL scripts, copy .dll files, or right-click "Publish" in Visual Studio, you cannot safely modernise it. Refactoring demands frequent, low-risk deployments, whereas manual deployments are infrequent and high-risk. Before touching the core architecture, codify those "magic" steps into a CI/CD pipeline using tools like GitHub Actions or Azure DevOps. Knowing you can reliably compile, test, and deploy at the push of a button removes a massive amount of stress from your rescue mission.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotyt1ibrgnia0xw65zlf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotyt1ibrgnia0xw65zlf.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Rescuing legacy software isn't about writing the cleverest C#; it’s about managing risk. These five strategies are just the beginning of making controlled, incremental changes to maintain or modernise a legacy .NET system.&lt;/p&gt;

&lt;p&gt;Don’t be put off if you inherit a complex legacy application. By building a test safety net, making small controlled changes, ensuring code is testable, and automating your deployments, you can transform a terrifying legacy system into a manageable one. With patience, care, and the right workflow, any codebase can be brought under control to meet your business needs.&lt;/p&gt;




&lt;p&gt;1: &lt;em&gt;Feathers, Michael C. (2005). Working Effectively with Legacy Code&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>legacycode</category>
      <category>devops</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>In Memory Automated UI Testing ASP.NET Core</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Fri, 05 Nov 2021 15:53:04 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/in-memory-automated-ui-testing-aspnet-core-106d</link>
      <guid>https://dev.to/jdinnovensa/in-memory-automated-ui-testing-aspnet-core-106d</guid>
      <description>&lt;h2&gt;Introduction&lt;/h2&gt;

&lt;p&gt;In this article we look at how to run in-memory automated UI tests for an ASP.NET Core web app using Playwright and NUnit.&lt;/p&gt;

&lt;p&gt;The article provides demo code and solutions to issues found along the way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8h42milcnnqqfbnomk8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8h42milcnnqqfbnomk8.jpg" alt="Wet tennis ball rotating quickly" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Automated UI Testing&lt;/h2&gt;

&lt;p&gt;Automated testing of any web application is essential to ensure it functions correctly.  On the top of the “testing pyramid” proudly sits UI testing or end-to-end testing, above integration and unit testing.  Automated UI testing involves launching and controlling a browser to drive through a set of interactions that a user would perform.  Assertions are made to see if the web app and browser behave as expected.  Browsers are controlled by testing tools such as Selenium,  Puppeteer or the new kid on the block, Playwright.&lt;/p&gt;

&lt;p&gt;Normally, tests are run against a web application deployed to a test environment which has been configured with the same infrastructure as the production environment.  However, in this post we will be looking at running our UI tests against an &lt;em&gt;in-memory&lt;/em&gt; instance of a web application.  By running the test in this way, we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Develop tests faster.&lt;/li&gt;
&lt;li&gt;Reconfigure the app with test configuration.&lt;/li&gt;
&lt;li&gt;Catch some errors earlier in the dev/test process.&lt;/li&gt;
&lt;li&gt;Still run the tests against a deployed environment later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we get started, we need to take a small step back.&lt;/p&gt;

&lt;h2&gt;Integration testing with WebApplicationFactory&lt;/h2&gt;

&lt;p&gt;Microsoft provide a neat way to run &lt;a title="ASP.NET Core integration testing" href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests" rel="noopener noreferrer"&gt;integration tests&lt;/a&gt; using an in-memory web server called &lt;em&gt;TestServer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;WebApplicationFactory&lt;/code&gt; class will provide you with a running TestServer.  It requires a type in the entry point assembly of the application. Typically the Startup or Program classes can be used.  The &lt;code&gt;WebApplicationFactory&lt;/code&gt; class also provides a virtual &lt;code&gt;ConfigureWebHost()&lt;/code&gt; method, which allows for the web host to be manipulated.  This is powerful and means services can be removed, added, or swapped.  For example, a SQL Server database can be removed and replaced with an in-memory database.&lt;/p&gt;

&lt;p&gt;A unit test framework such as xUnit or NUnit can be used to write and run the tests targeting the app hosted in the in-memory TestServer.  When tests run they use an &lt;code&gt;HttpClient&lt;/code&gt; connection given by the TestServer.  The &lt;code&gt;HttpClient&lt;/code&gt; is a C# class and is not a browser, which means its a bit limited and inconvenient to use.&lt;/p&gt;

&lt;p&gt;Using the TestServer approach, we have an in-memory web server at our disposal.  Not only that, it provides a way to override the way the webhost is built so that we can customise our application for our testing needs.  That sounds great! Unfortunately, when using a browser automation tool, such as Selenium or Playwright, the TestServer approach does not quite work.&lt;/p&gt;

&lt;h2&gt;Modifying Test Server to Work with Selenium and Playwright&lt;/h2&gt;

&lt;p&gt;After reading some really useful articles by &lt;a href="https://www.benday.com/2021/07/19/asp-net-core-integration-tests-with-selenium-webapplicationfactory/" rel="noopener noreferrer"&gt;Ben Day&lt;/a&gt;, &lt;a href="https://blog-bertrand-thomas.devpro.fr/2020/01/27/fix-breaking-change-asp-net-core-3-integration-tests-selenium/" rel="noopener noreferrer"&gt;Bertrand Thomas&lt;/a&gt;  and an oldie from &lt;a href="https://www.hanselman.com/blog/real-browser-integration-testing-with-selenium-standalone-chrome-and-aspnet-core-21" rel="noopener noreferrer"&gt;Scott Hanselman&lt;/a&gt; (thanks guys!), I’ve derived a class from &lt;code&gt;WebApplicationFactory&lt;/code&gt; which works with browser testing tools including Selenium and Playwright. In &lt;a title="Demo project on GitHub" href="https://github.com/JD-Innovensa/InMemoryPlaywrightDemo" rel="noopener noreferrer"&gt;the demo project&lt;/a&gt;, this is the &lt;code&gt;AutomatedTestServerFactory&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;One of the main issues when using a TestServer with a browser testing tool is that &lt;code&gt;ConfigureWebHost()&lt;/code&gt; is not called.  This means the app cannot be changed at start up to use a test configuration.  In the “&lt;em&gt;traditional&lt;/em&gt;” way of using TestServer, we are given an &lt;code&gt;HttpClient &lt;/code&gt;instance for our request/response interaction with the server.  The &lt;code&gt;HttpClient &lt;/code&gt;instance is created using the &lt;code&gt;CreateClient()&lt;/code&gt; method in &lt;code&gt;WebApplicationFactory&lt;/code&gt;, which in turn runs the &lt;code&gt;ConfigureWebHost()&lt;/code&gt; method.  So, the really useful re-configuration we need is only done when an &lt;code&gt;HttpClient &lt;/code&gt;is created! Browser testing tools effectively replace the client and are controlled independently of the TestServer.&lt;/p&gt;

&lt;p&gt;To get round this issue, &lt;code&gt;AutomatedTestServerFactory&lt;/code&gt;  sets the configuration when the webhost is built.  The work is mainly done in two methods &lt;code&gt;CreateServer()&lt;/code&gt; and &lt;code&gt;CreateWebHostBuilder()&lt;/code&gt;, which are called by the constructor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateWebHostBuilder()&lt;/code&gt; is called first and sets the web host configuration, which is passed through as a constructor parameter.  Also optionally set is the environment, otherwise the default environment is Production, which may not be ideal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo9ha1rsauha73yy8n2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo9ha1rsauha73yy8n2q.png" alt="Code from CreateWebHostBuilder method" width="640" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateServer()&lt;/code&gt; builds and starts the web application and begins listening for requests. This method returns a null TestServer.  I didn’t see any impact of this and it avoids a new and different instance of the web app from being created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qs3xisoljqyaxu9mpjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qs3xisoljqyaxu9mpjd.png" alt="Code from CreateServer method" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these changes in place, we have a WebApplicationFactory that creates an in-memory TestServer hosting our re-configured web application and is suitable for use with Selenium or Playwright.   Super!&lt;/p&gt;

&lt;h2&gt;Using Selenium or Playwright&lt;/h2&gt;

&lt;p&gt;When developing this project, I started using Selenium as the browser testing tool, however I noticed in October 2021, that Microsoft updated the integration test article (see above) to recommend Playwright instead of Selenium.  So I took a look, I liked it, and I have used Playwright for the remainder of the project.  &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; is a relatively new end-to-end testing tool developed and maintained by Microsoft.&lt;/p&gt;

&lt;p&gt;The reasons I liked Playwright include its simplification of browser driver management, that it works with Chromium, Firefox and Webkit browsers and the ease of use of the API compared to Selenium’s.  Out of interest, I found migrating tests from Selenium to Playwright to be straightforward too!&lt;/p&gt;

&lt;p&gt;The decision to use Playwright also led to selecting NUnit as the test runner.  Typically, I use xUnit for testing, but it doesn't support &lt;a title="xUnit support for Playwright" href="https://playwright.dev/dotnet/docs/test-runners#xunit-support" rel="noopener noreferrer"&gt;running Playwright tests in parallel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Configuring Playwright on local PC&lt;/h3&gt;

&lt;p&gt;To get Playwright to work, there are two steps required using Package Manager Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Playwright CLI as a global tool.
&lt;ul&gt;
&lt;li&gt;Run the command: &lt;code&gt;install -g Microsoft.Playwright.CLI&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Install browsers.
&lt;ul&gt;
&lt;li&gt;Navigate to the path of the test project and run the command: &lt;code&gt;playwright install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Code Time!&lt;/h2&gt;

&lt;p&gt;In order to demonstrate this working, I’ve created a &lt;a href="https://github.com/JD-Innovensa/InMemoryPlaywrightDemo" rel="noopener noreferrer"&gt;demo application available on GitHub&lt;/a&gt;.  The codebase has three parts as described below.&lt;/p&gt;

&lt;h3&gt;Employee System&lt;/h3&gt;

&lt;p&gt;This app is a very simple ASP.NET Core MVC application using EntityFrameworkCore with SqlClient. The only functionality is a homepage and a page returning a list of employees.  This is enough for our purposes though!  It has a migration script, so remember to run &lt;code&gt;update-database&lt;/code&gt; before launching it.&lt;/p&gt;

&lt;p&gt;This is what the Employee List page looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx4e73n5ni9nx8tvquaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx4e73n5ni9nx8tvquaj.png" alt="Table of employees in demo app." width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Test code&lt;/h3&gt;

&lt;p&gt;The test code is comprised of two libraries, AutomatedTesting and Employees.UITests.  The AutomatedTesting library contains the custom &lt;code&gt;AutomatedTestServerFactory &lt;/code&gt;class to enable in-memory testing with Selenium or Playwright. &lt;/p&gt;

&lt;p&gt;Employees.UITests is an NUnit test library.  It contains a base class, &lt;code&gt;BaseTestClass&lt;/code&gt;, which is responsible for creating the web server and passing in the required configuration, in this case changing the database to in-memory and adding test data.  The base class is decorated with the attribute &lt;code&gt;[Parallelizable(ParallelScope.Children)]&lt;/code&gt; to allow child tests to run in parallel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9415z8crk8xyfhjtq1fg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9415z8crk8xyfhjtq1fg.png" alt="Code of BaseTestClass" width="775" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EmployeeListTests &lt;/code&gt;class inherits from &lt;code&gt;BaseTestClass &lt;/code&gt;and focusses on the tests, so the code is quite clean. There are three tests, each one is the same, but using different browsers.  Running the test project will cause all three tests to run in parallel.  Finally, I’ve used FluentAssertions (&lt;a title="10 ten tips on Fluent Assertions" href="../fluent-assertions-10-top-tips" rel="noopener"&gt;as I like it&lt;/a&gt;!) for evaluating the outcomes of tests.&lt;/p&gt;

&lt;p&gt;In the excerpt below, the Employee menu option is clicked and assertions are made on the output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4lkdfgxmraptcdkymwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4lkdfgxmraptcdkymwm.png" alt="Fragment of test code using Playwright" width="752" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have started to pull out some common tasks in &lt;code&gt;TestHelper&lt;/code&gt;, such as taking a screenshot and attaching it to the test report.&lt;/p&gt;

&lt;p&gt;After running the tests, you can see the results in the test log by right-clicking on a test and opening the log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkchj2qxzccv1vkdcopd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkchj2qxzccv1vkdcopd.png" alt="" width="472" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Visual Studio 2019, the results include both the log messages and the screenshots.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wifxtcq5g929us39rjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wifxtcq5g929us39rjr.png" alt="Test log" width="525" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Devops&lt;/h3&gt;

&lt;p&gt;I have included a yaml build pipeline for Azure DevOps.  This configuration deals with the Playwright requirements, builds the code and executes the tests in a cloud-hosted Windows server.  It can target a self-hosted Windows target too.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;Resolved Issues&lt;/h2&gt;

&lt;p&gt;Developing this project was not without its issues.  Apart from getting the AutomatedTestServerFactory to work, here are the issues I found and overcame.&lt;/p&gt;

&lt;h3&gt;Issues found on Developer PC&lt;/h3&gt;

&lt;h4&gt;Issue 1:&lt;/h4&gt;

&lt;p&gt;The web application launches, but is missing styling, JavaScript and images. This was because the test server would launch in the bin directory of the test project and not have a reference to the wwwroot folder. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5w7v6470nraahqm5gi6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5w7v6470nraahqm5gi6d.png" alt="" width="741" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: &lt;/strong&gt;This was resolved by adding a post-build target to the Employees.UITests project file.  The target copies the wwwroot folder recursively from the Employees.UI project to the Employees.UITests\bin folder. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswageep6cu0g5hn5o3x2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswageep6cu0g5hn5o3x2.png" alt="" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/copy-task?view=vs-2019#example-2" rel="noopener noreferrer"&gt;Microsoft have an example of how to copy files recursively during after build.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;Issue 2:&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;span&gt;Microsoft.Playwright.PlaywrightException : Executable doesn't exist&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: One or more of the browsers is missing.  Run &lt;code&gt;&lt;span&gt;playwright install&lt;/span&gt;&lt;/code&gt; command in the Package Manager Console, but significantly make sure this is done in the directory of the test project.&lt;/p&gt;

&lt;h3&gt;Issues found in Azure Devops&lt;/h3&gt;

&lt;h4&gt;Issue 3:&lt;/h4&gt;

&lt;p&gt;Install step of Playwright Tool causes error when re-run.  The following two lines in the log file indicate the issue.&lt;/p&gt;

&lt;pre&gt;Tool 'microsoft.playwright.cli' is already installed.&lt;br&gt;Error: The process 'C:\Program Files\dotnet\dotnet.exe' failed with exit code 1&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo7kfy3rtksq470kxer7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo7kfy3rtksq470kxer7.png" alt="Playwright install error in Azure Devops" width="637" height="61"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Instead of calling &lt;code&gt;dotnet install&lt;/code&gt;, run the &lt;code&gt;dotnet update&lt;/code&gt; command as this removes and clean installs the tool.&lt;/p&gt;

&lt;h4&gt;Issue 4:&lt;/h4&gt;

&lt;p&gt;Error: &lt;strong&gt;&lt;span&gt;Cannot find a tool in the manifest file that has a command named 'playwright'.&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Make sure the playwright install command runs in the directory of the test project.&lt;/p&gt;

&lt;h4&gt;Issue 5:&lt;/h4&gt;

&lt;p&gt;Cloud-hosted Chrome fails with exception: &lt;span&gt;&lt;strong&gt;Microsoft.Playwright.PlaywrightException : net::ERR_CERT_AUTHORITY_INVALID at &lt;a href="https://localhost:5001/" rel="noopener noreferrer"&gt;https://localhost:5001/&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Get a new browser instance to get round this issue using the code below.  This effectively runs the tests in incognito mode.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true });&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;Issue 6:&lt;/h4&gt;

&lt;p&gt;Firefox does not navigate to test site.  Instead a warning is displayed in the browser&lt;/p&gt;

&lt;p&gt;&lt;span&gt;“Warning: Potential Security Risk Ahead”&lt;/span&gt;.&lt;/p&gt;

&lt;p&gt;Solution: Firefox prevents navigation to another port on localhost.  The warning requires manual intervention, but the settings can be overridden so that the automated test can run.  To do so, set the following capabilities (&lt;a href="https://medium.com/volosoft/how-to-disable-firefox-warning-potential-security-risk-ahead-f081fbf81a4f" rel="noopener noreferrer"&gt;these settings are briefly covered here&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;caps.Add("security.insecure_field_warning.contextual.enabled", false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;caps.Add("security.certerrors.permanentOverride", false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;caps.Add("network.stricttransportsecurity.preloadlist", false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;caps.Add("security.enterprise_roots.enabled", true);&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;Other points:&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Firefox not waiting for AJAX request to complete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When waiting for a page to load that fires off an AJAX request, for example fetching data on page load, Firefox does not seem wait.  To resolve add &lt;code&gt;await WaitForLoadStateAsync()&lt;/code&gt; after the command to load the page.&lt;/p&gt;

&lt;ol start="2"&gt;
&lt;li&gt;Azure DevOps only includes NUnit log on test failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NUnit provides the convenient &lt;code&gt;TestContext.Out&lt;/code&gt; to log messages during testing.  These will always appear using the test runner in Visual Studio 2019, but these messages are only present in Azure Devops when a test fails.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this article we have explored through the code, configuration and gotchas of running automated UI tests of an ASP.NET Core web app using Microsoft’s TestServer.  We have been able to perform UI testing using Playwright with multiple browsers running in parallel using NUnit.&lt;/p&gt;

&lt;p&gt;We have seen a number of issues, mainly arising in Azure DevOps, and how to overcome them.&lt;/p&gt;

&lt;p&gt;In-memory automated UI testing could be great for smaller systems and to help develop test code. It's not an approach for all cases, but certainly has its merits.&lt;/p&gt;

&lt;p&gt;Finally, if you’ve not used Playwright for automated testing, perhaps it is a good opportunity to try it out!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>csharp</category>
      <category>testing</category>
    </item>
    <item>
      <title>Practical complex data for unit testing</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Thu, 09 Sep 2021 12:44:28 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/practical-complex-data-for-unit-testing-5dh3</link>
      <guid>https://dev.to/jdinnovensa/practical-complex-data-for-unit-testing-5dh3</guid>
      <description>&lt;p&gt;Unit testing is a long-established and essential part of the software development process particularly when using .NET.  The basic premise, if you are not aware, is to test the public methods of classes so that they work as expected. &lt;/p&gt;

&lt;p&gt;Unit tests follow the 3As: &lt;em&gt;Arrange&lt;/em&gt;, &lt;em&gt;Act &lt;/em&gt;and &lt;em&gt;Assert&lt;/em&gt;. In other words, set up the conditions for the test, run the code and finally, check that it worked as expected. If it did, great the test passed!  If not, then there’s some work to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc1_637662758481588575.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc1_637662758481588575.png" alt="" width="404" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article takes a look at the first step, arranging the test.  Although there are a few things to do during this stage, right now we are only concerned with creating the data required to run the test.&lt;/p&gt;

&lt;p&gt;Later, we are going to look at how to create complex test data that can be easily understood by using the Builder pattern.&lt;/p&gt;

&lt;h2&gt;Why is it important how test data is created?&lt;/h2&gt;

&lt;p&gt;Well, the test data can be confusing to look at and to understand.  It becomes time-consuming to follow and to update. This is especially the case where the test data has relationships to be maintained. &lt;/p&gt;

&lt;p&gt;If the test data is not clear it is easy to make a mistake and introduce bugs into test code.&lt;/p&gt;

&lt;p&gt;Test data which was written a long time ago becomes hard to recall.  As with everything it is fresh in the mind when it is written, but three or six months later, it becomes difficult to recall the intent of the data.&lt;/p&gt;

&lt;p&gt;It is easy and very tempting at times to break the &lt;a title="Read about the DRY principle" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt; (Don't Repeat Yourself) principle and just repeat test data.  As new tests are set up, test data that is not well understood is typically copied and tweaked to suit the purpose.  This just increases the technical debt within the test data making the whole project harder to maintain.&lt;/p&gt;

&lt;h2&gt;Arranging test data&lt;/h2&gt;

&lt;p&gt;Most, if not all code depends on parameters or data to run.  So when it comes to unit testing and asserting that the code works some amount of data is usually required to run the test.  This can range from a simple value-type parameter to a complex object model, or even a larger set of related data. &lt;/p&gt;

&lt;p&gt;There are different ways of creating unit test data and we’ll take a look at some of them briefly.&lt;/p&gt;

&lt;p&gt;Test data can be created in a unit test directly, passed into a test in line, loaded from files, or created for use in an in-memory database. I consider in-memory database fine for unit testing.  A separate database hosted outside the test execution process would be considered integration testing.&lt;/p&gt;

&lt;h2&gt;Adding test data with xUnit Unit Test Framework&lt;/h2&gt;

&lt;p&gt;To manage and run my unit tests I use &lt;a title="xUnit website" href="https://xunit.net/" rel="noopener noreferrer"&gt;xUnit&lt;/a&gt;. It is a popular, free, open source and community-focused unit testing framework. xUnit provides a few useful ways to manage our test data.&lt;/p&gt;

&lt;h3&gt;Theory Unit Test&lt;/h3&gt;

&lt;p&gt;Theory unit tests allow a single test to act on sets of parameterised data .  Data is passed using the InlineData data attribute for as many different cases as needed.  This keeps the test code DRY and removes the need to repeat the test logic for different data values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc2_637662776465999193.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc2_637662776465999193.png" alt="" width="452" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In cases where test data is complex, xUnit also supports ClassData and MemberData data attribute for loading test data in from a method or from another class.  xUnit is extensible and custom data attributes can be written, for example, to &lt;a href="https://andrewlock.net/creating-a-custom-xunit-theory-test-dataattribute-to-load-data-from-json-files/" rel="noopener noreferrer"&gt;load test data from files&lt;/a&gt; or from a database.&lt;/p&gt;

&lt;h3&gt;Fixtures&lt;/h3&gt;

&lt;p&gt;The final xUnit feature to look at is the Fixture class. Fixtures allow for sharing setup and clean up code for all tests in a class or even in multiple test classes.  This is a great place to create complex test data and the perfect place for creating an in-memory database for the tests that require one.&lt;/p&gt;

&lt;p&gt;For our development we often use SQL Server accessed via Entity Framework and to test we use SQLite in-memory database to simulate the database layer. We use this approach to initialise an in-memory database with the data we need which provides the context for a series of tests logically grouped in a class.&lt;/p&gt;

&lt;h3&gt;AutoFixture&lt;/h3&gt;

&lt;p&gt;One neat library for creating and generating test data is &lt;a href="https://github.com/AutoFixture/AutoFixture" rel="noopener noreferrer"&gt;AutoFixture&lt;/a&gt;.  The creators describe it as follows:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AutoFixture is an open source library for .NET designed to minimize the 'Arrange' phase of your unit tests in order to maximize maintainability. Its primary goal is to allow developers to focus on what is being tested rather than how to setup the test scenario, by making it easier to create object graphs containing test data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We don’t use AutoFixture for our test data creation in this example, but it is worth mentioning as it can speed up the process of writing tests.&lt;/p&gt;

&lt;h2&gt;How the Builder Pattern can help create complex data&lt;/h2&gt;

&lt;p&gt;As touched upon before, there are issues that emerge when test data gets complex.  Thankfully there is an approach which we can use to help us and has been around for a long time.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Builder_pattern" rel="noopener noreferrer"&gt;builder pattern is a well-understood design pattern&lt;/a&gt; in software development.  It allows the construction of complex objects step by step and can be used to create different representations of complex data.&lt;/p&gt;

&lt;p&gt;This is great for our needs as we can use this approach to create our test data.  The builder pattern enables us to create our test data with the intent clear. It does mean creating a concrete implementation of the pattern using knowledge of the domain. &lt;/p&gt;

&lt;p&gt;However, I think this approach can be worthwhile as it gives us the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single place to instantiate objects&lt;/li&gt;
&lt;li&gt;It leads to preventing the repetition of data&lt;/li&gt;
&lt;li&gt;Provides a way of controlling each step of the construction process. This leads to the code providing a clear intent of the data.&lt;/li&gt;
&lt;li&gt;The concrete implementation can be developed using domain-specific language. This also makes the intent of the data clearer. Name the builder methods as you wish to provide the most information to the developer/tester.&lt;/li&gt;
&lt;li&gt;As the construction of the object is encapsulated in code, it can be designed so that foreign keys are handled in code which reduces complexity.&lt;/li&gt;
&lt;li&gt;Also, it means collections are created when required. Collections can be navigated by accessing the last item by convention.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Builder Pattern In Action&lt;/h2&gt;

&lt;p&gt;For this project I used .NET Core 5.0, EFCore and SQLite.  Also included is xUnit for unit testing, AgileMapper for mapping and Fluent Assertions for the assertions.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://github.com/JD-Innovensa/TestDataBuilderDemo" rel="noopener noreferrer"&gt;demo project is a nascent itinerary planner and is available on GitHub&lt;/a&gt;. It has a model based around tourists who have excursions containing visits to places (e.g. Paris) and visits to points of interests (e.g. Eiffel Tower).&lt;/p&gt;

&lt;p&gt;The only bit of business logic is to swap the order of places to visit in an excursion.  This logic is the code under test, has a dependency on a database and is encapsulated in a &lt;code&gt;TouristService&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc3_637662809038343271.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc3_637662809038343271.png" alt="" width="748" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now for the data.  As mentioned before, we will simulate a database using an in-memory SQLite database as this will work nicely with our service under test.  This will be added using an xUnit fixture class so that all tests in the class can share the same data.  Finally, we have our custom implementation of the builder pattern used to create the test data.&lt;/p&gt;

&lt;h3&gt;What does the test builder look like?&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ITourist&lt;/code&gt; interface contains methods to create our Tourist. You can see that the builder enables the complex object model to be built step by step using method names that have domain relevance. Finally the &lt;code&gt;BuildTourist&lt;/code&gt; method will return our object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc4_637662809039652693.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc4_637662809039652693.png" alt="" width="766" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for a sample of the implementation. &lt;/p&gt;

&lt;p&gt;Initially, we create our top-level object, a &lt;code&gt;Tourist&lt;/code&gt; which is kept in a private field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc5_637662809039843065.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc5_637662809039843065.png" alt="" width="643" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we add an &lt;code&gt;Excursion &lt;/code&gt;to our tourist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc6_637662809040100964.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc6_637662809040100964.png" alt="" width="736" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All of the other steps are similar to the above to build up the object.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;BuildTourist &lt;/code&gt;method is implemented which returns our &lt;code&gt;Tourist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc7_637662809040290097.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc7_637662809040290097.png" alt="" width="236" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The test project includes an example of loading the data using the Builder pattern.  The order and indentation of items is important for readability, but not essential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc8_637662809040485764.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc8_637662809040485764.png" alt="" width="756" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For contrast, it also includes an example of loading the same test data by direct object creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc9_637662809040706277.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc9_637662809040706277.png" alt="" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparing these two different implementations it is easy to see that using a Builder, the test data is more readable and easier to maintain.&lt;/p&gt;

&lt;p&gt;Now on to the unit tests.  The &lt;code&gt;TestsWithFixtureAndBuilder &lt;/code&gt;class uses the Fixture where our test data is created.  The test class contains a single test to check that the &lt;code&gt;SwapPlaceVisits &lt;/code&gt;method works as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc10_637662809041203367.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fc10_637662809041203367.png" alt="" width="642" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember to check out the &lt;a href="https://github.com/JD-Innovensa/TestDataBuilderDemo" rel="noopener noreferrer"&gt;entire code example in GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Further ideas for the Test Data Builder&lt;/h2&gt;

&lt;p&gt;As this is custom code, there is a huge amount of flexibility available and you can enhance the basic test data builder to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include validation rules. This can be useful for ensuring the business logic constraints are correct before running a test and ensuring your test data is correctly formed.&lt;/li&gt;
&lt;li&gt;Set the primary keys automatically. Depending on how the tests are set up in your specific case, there could be the chance to set the primary keys in the builder.&lt;/li&gt;
&lt;li&gt;Create model with default values and update specific values only. This can be done using lambda functions.&lt;/li&gt;
&lt;li&gt;Add plain English descriptions to the builder and override the ToString() method. Textual information can be added inline.   This could also be outputted during unit test execution to help debug problematic tests.&lt;/li&gt;
&lt;li&gt;Add factory methods to generate common test data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;As we’ve seen, there are many ways of creating test data. However, when the data starts to become complex it can become difficult to understand and maintain and lead to complications with our unit tests.  The builder pattern has been around for a long time and it has proven to be useful when creating test data to give context and make the test data easy to understand and maintain.&lt;/p&gt;

&lt;p&gt;Hopefully this article and code example will provide some useful pointers to building your test data.  Beyond this specific code example, the take home points are to find the approach which works best for you and the rest of your development and testing team.  So when creating test data, aim for the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The test data is easy to understand.&lt;/li&gt;
&lt;li&gt;The relationship of the data is easy to understand.&lt;/li&gt;
&lt;li&gt;The data is easy to change or add.&lt;/li&gt;
&lt;li&gt;The approach ensures developers stick to the DRY principle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>testing</category>
    </item>
    <item>
      <title>Fluent Assertions 10 Top Tips</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Tue, 22 Jun 2021 11:29:09 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/fluent-assertions-10-top-tips-409a</link>
      <guid>https://dev.to/jdinnovensa/fluent-assertions-10-top-tips-409a</guid>
      <description>&lt;h2&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;The value of writing unit tests is well understood these days and there are some really useful frameworks and tools which developers depend upon.&lt;/p&gt;

&lt;p&gt;One such library is &lt;a href="https://fluentassertions.com/" rel="noopener nofollow noreferrer"&gt;Fluent Assertions&lt;/a&gt;.  It is an open source library that integrates with several Microsoft platforms such as .NET or .NET Core, and supports several testing frameworks including MS Test, NUnit and XUnit.  The main motivation for its development is to show clear error messages returned by failed tests. Fluent Assertions has many extension methods which can improve the efficiency and readability of unit test code.  The project allows for descriptive outcomes which suits TDD and BDD testing methodologies, but it is useful even if unit tests are written after the main code!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fgreentesttubes_637599504912048371.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Fgreentesttubes_637599504912048371.jpg" alt="" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our &lt;a href="https://www.innovensa.co.uk/services/custom-software-development" rel="noopener noreferrer"&gt;backend development&lt;/a&gt;, we &lt;a href="https://www.innovensa.co.uk/about/technology-we-use" rel="noopener noreferrer"&gt;code mostly in C#&lt;/a&gt; and use XUnit as our testing framework. We introduced Fluent Assertions on a project a couple of years ago to help make our assertions cleaner as we particularly liked the concept of Assertion Scopes (more on that later).  Since then, we’ve continued to use Fluent Assertions, but we have used it in a limited way using the same set of features.&lt;/p&gt;

&lt;p&gt;However, recently I felt it was time for a knowledge refresh of Fluent Assertions and it has revealed plenty of opportunities for us to improve our unit test code.&lt;/p&gt;

&lt;p&gt;This article covers my ten top tips for using Fluent Assertions from this recap.  If you are using this library in your tests, then hopefully there’s a tip or two that helps you out.  This article covers the features that have jumped out at me, but there are many more useful features in the library.&lt;/p&gt;

&lt;p&gt;It is assumed that you have a basic knowledge of Fluent Assertions or are actively using it. If you don’t, fear not, you can always skim through the &lt;a href="https://fluentassertions.com/introduction" rel="noopener nofollow noreferrer"&gt;docs&lt;/a&gt; later!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;10 Top Fluent Assertion Tips&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;So, here are my ten top tips that I will keep in mind from now on when writing unit tests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At the time of writing Fluent Assertion v5.10.3 was used.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;1. Use object graph comparison&lt;/h3&gt;

&lt;p&gt;Some of the early test code we wrote was comparing object graphs and we were writing out property to property comparisons.  As you can imagine, this was quite tedious!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-1_637598919630980626.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-1_637598919630980626.png" alt="C# unit test code" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fluent Assertions has a really useful extension method &lt;code&gt;BeEquivalentTo()&lt;/code&gt;.   This allows object graph comparison and therefore removes the need to list all properties for comparison.  This reduces the time required to write the test code and there is less of it to read and maintain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-2_637598919631934920.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-2_637598919631934920.png" alt="Use of BeEquivalentTo() method" width="800" height="41"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BeEquivalentTo()&lt;/code&gt; is very powerful and has plenty of options.  Among its features, you can select properties and/or fields to include or exclude, specify collection order, and it has a diagnostics mode to help debug deep into object graphs.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;2. Use Date formatting extension methods&lt;/h3&gt;

&lt;p&gt;There are a set of date formatting extension methods which allow for dates and times to be written more expressively.  This approach provides clarity which is useful for non-developers to understand the test code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-3_637598921884336881.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-3_637598921884336881.png" alt="Date formatting extension method usage" width="432" height="30"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;3. Use DateTime and TimeSpan assertions&lt;/h3&gt;

&lt;p&gt;There are lots of useful assertions for dates and times and using them helps to make the code more readable.  Use these extensions to replace the traditional code comparison operators. &lt;/p&gt;

&lt;p&gt;One other point to mention, a date can be checked that it falls within a certain timeframe.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-4_637598921885745818.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-4_637598921885745818.png" alt="Date extension method" width="800" height="27"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt; &lt;/h2&gt;

&lt;h3&gt;4. Use Collection extension methods&lt;/h3&gt;

&lt;p&gt;Beyond the typical collection assertion methods you would expect to find, such as checking the count, there are some powerful methods to perform assertions on collections and the items they contain.  These include asserting the order of the items, checking for uniqueness and whether a collection is a subset of another collection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-5_637598921885985418.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-5_637598921885985418.png" alt="Collection extension methods" width="492" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other powerful methods include &lt;code&gt;Contain&lt;/code&gt; and &lt;code&gt;ContainSingle&lt;/code&gt;, these help validate that at least one or exactly one item in the collection meets a criteria.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;SatisfyRespectively&lt;/code&gt; is a method which allows you to perform complex assertions on each item in a collection.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;5. Use Which&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Which&lt;/code&gt; extension allows for another assertion to be chained onto a previous assertion that identifies a single item in a collection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-6_637599466682101576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-6_637599466682101576.png" alt="Use of Which() extension method" width="800" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt; &lt;/h2&gt;

&lt;h3&gt;6. Use Exception Assertions&lt;/h3&gt;

&lt;p&gt;In our code we are using the exception assertions for top-level exception type checking.  However, there is more that can be done! Using &lt;code&gt;WithMessage&lt;/code&gt; allows us to check the message content is as expected.  Also, it is possible to check the inner exception and the individual properties of the exception.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;7. Use Perform Assembly Assertions&lt;/h3&gt;

&lt;p&gt;In many of the systems we develop we use a layered architecture, specifically the &lt;a href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/" rel="noopener nofollow noreferrer"&gt;Onion Architecture&lt;/a&gt;. This architecture states that code can only reference immediate inner layers. Fluent Assertions provides a pair of useful assertions to check that this rule is enforced at the assembly level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-7_637599466683783725.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Ffa-code-7_637599466683783725.png" alt="Use of Reference() and NotReference() extension methods" width="597" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;8. Improve Should().BeTrue() code&lt;/h3&gt;

&lt;p&gt;Ok, so this is actually a tip from the Fluent Assertions documentation itself!  If the code is not evaluating a Boolean directly, then it is likely that &lt;code&gt;Should().BeTrue()&lt;/code&gt; assertions can be written using a more expressive assertion syntax.  This will improve readability and the error message displayed. There are myriad examples in the &lt;a href="https://fluentassertions.com/tips/#improved-assertions" rel="noopener nofollow noreferrer"&gt;documentation&lt;/a&gt; to check out.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;9. Use AssertionScope&lt;/h3&gt;

&lt;p&gt;This is something that we already use and it is quite a fundamental feature, but it is worthwhile mentioning.  By using the &lt;code&gt;using (new AssertionScope()) { }&lt;/code&gt; code block, all assertions are evaluated and if the test fails, the error will include all messages from multiple failed assertions.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;10. Other points&lt;/h3&gt;

&lt;p&gt;Finally, a couple of other things were noticed when reading through the docs.  Neither of these features we currently need to use, but they are now kept in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can perform execution time assertions to check code performance.&lt;/li&gt;
&lt;li&gt;Can perform XML assertions on LINQ-to-XML classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;We like Fluent Assertions for the benefits of clear error messages, more readable test code and that fewer lines of code are needed for our tests.  We are pleased to have refreshed our knowledge in this area and we can improve our future tests as well as refactor older ones.&lt;/p&gt;

&lt;p&gt;This exercise taught us an important lesson in general.  The world of software development is ever-changing and it pays dividends to keep up to date with existing tools.&lt;/p&gt;

&lt;p&gt;Refreshing your knowledge of the libraries, tools and frameworks you use will provide valuable benefits to you, your code and those you work for.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>csharp</category>
      <category>testing</category>
    </item>
    <item>
      <title>Our top tech news &amp; programming sites</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Fri, 11 Jun 2021 10:59:33 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/our-top-tech-news-programming-sites-2gop</link>
      <guid>https://dev.to/jdinnovensa/our-top-tech-news-programming-sites-2gop</guid>
      <description>&lt;p&gt;Did you know that there are just sixty years between the very first flight and the presence of the first man on the moon? That’s an incredible rate of technical innovation.&lt;/p&gt;

&lt;p&gt;Today, it seems that technology advances at an ever-increasing pace.  As businesses and individuals have become more digitally dependent, investment has flowed in to the tech sector and innovation has flourished creating a rich diversity of devices, platforms, development languages.  &lt;/p&gt;

&lt;p&gt;To see how quickly things change you just need to cast your mind back to the year 2000. This was a time before today’s age of cloud computing, smartphones, internet of things (IoT) and big data. Wow, so much has changed!&lt;/p&gt;

&lt;p&gt;However, for all the success stories, there are failures and certain advances which have not caught on.  Also, even over a relatively short period of time the latest and greatest concept can be quickly forgotten with something new and improved.&lt;/p&gt;

&lt;p&gt;Part of the role at a software development company is to keep abreast of these technology changes.  It is a continuous exercise to decide on which technology is here for the long-term, when to be ready to shift when something is rapidly losing its place, and to avoid the also-rans.  There are a few ways this is done, but one of them is as simple as reading and keeping well informed.&lt;/p&gt;

&lt;p&gt;Fortunately the internet contains a huge resource of written content, and technologists and experts are generally happy to share their knowledge. &lt;/p&gt;

&lt;p&gt;This article covers some of our favourite places we often visit to remain au fait with the technology trends which are most relevant to software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Register
&lt;/h2&gt;

&lt;p&gt;The Register is a popular technology news site in the UK and covers all major stories that we need to know about.  It has a humorous edge to its content which makes it a pleasure to read even about quite dry topics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.theregister.com/" rel="noopener noreferrer"&gt;https://www.theregister.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Slashdot
&lt;/h2&gt;

&lt;p&gt;Slashdot is a social news site which covers technology, science and politics.  It has been running since 1997.  Submissions come from various sources so it is an effective way to get an aggregated news feed of sorts.  Despite its U.S leaning, it is possible to pick up essential technology news.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://slashdot.org/" rel="noopener noreferrer"&gt;https://slashdot.org/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Medium
&lt;/h2&gt;

&lt;p&gt;Medium is a popular blogging site for a range of topics and has been active since 2012. It aims to publish high quality  well-written articles and its content can be monetized.  It is another great resource for technical and programming content and tags can be used to filter content.&lt;/p&gt;

&lt;p&gt;Over the years, we have read many specialised articles to improve our knowledge. We also find many of the articles are opinionated which provides a good basis for discussion.  Reading the comments of these articles often provides a good insight on what a larger group of people think about a specific technology, tool or framework. At times this informs our decision making.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/tag/programming" rel="noopener noreferrer"&gt;https://medium.com/tag/programming&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev
&lt;/h2&gt;

&lt;p&gt;Dev is a developer community site where developers can contribute articles, be part of discussions and find out about products and services.  The site’s purpose is for collaboration and networked learning for developers.  Currently it boasts over 400,000 members.&lt;/p&gt;

&lt;p&gt;It is a lively community of developers and it is easy to find articles of interest as Azure, dotNET and C#.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/"&gt;https://dev.to/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DZone
&lt;/h2&gt;

&lt;p&gt;DZone is another great resource for us.  It boasts over 1 million members since being established in 2005.  The site covers news, tutorials and tools for programming, web development and DevOps and is useful for all ranges of developer ability.&lt;/p&gt;

&lt;p&gt;The site has a clean and well-categorised layout making it easy to dive into a specific area.  The site offers a range of technical publications for free.  This is a great way to become well-informed about a technology and gain insight into key industry trends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dzone.com/" rel="noopener noreferrer"&gt;https://dzone.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Channel 9
&lt;/h2&gt;

&lt;p&gt;Channel 9 sounds like a TV channel, but is actually a Microsoft site hosting an abundance of tutorial and learning material.  As we generally develop software using Microsoft technology, frameworks and tools, these videos are a great way for us to improve our knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://channel9.msdn.com/" rel="noopener noreferrer"&gt;https://channel9.msdn.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is a cross section of the range of sites which we use at &lt;a href="https://www.innovensa.co.uk/" rel="noopener noreferrer"&gt;Innovensa&lt;/a&gt; to keep informed about the world of software development and technology.  There are many other sites out there which we use alongside these and lots more that we simply don’t have time to use.   &lt;/p&gt;

&lt;p&gt;Using sites such as these is a great way for you to keep in touch with current technology and trends.&lt;/p&gt;

&lt;p&gt;What sites do you use? Leave us a comment.&lt;/p&gt;

</description>
      <category>news</category>
    </item>
    <item>
      <title>Comparison of Object Mapper Libraries</title>
      <dc:creator>Jon Dodd</dc:creator>
      <pubDate>Wed, 19 May 2021 09:44:41 +0000</pubDate>
      <link>https://dev.to/jdinnovensa/comparison-of-object-mapper-libraries-gm2</link>
      <guid>https://dev.to/jdinnovensa/comparison-of-object-mapper-libraries-gm2</guid>
      <description>&lt;p&gt;In this article we look at object to object mappers in C# .NET.  Mappers solve the common problem of writing mundane and repetitive code when assigning values between objects.  Three different mapping libraries are evaluated to give an idea of common themes and some differences which might be relevant for your development needs.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Farrows-GREEN_637569504131756774.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2Farrows-GREEN_637569504131756774.png" alt="" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The mapping libraries AutoMapper, AgileMapper and Mapster have been used in a simple demo program to explore some simple mapping scenarios.&lt;/p&gt;

&lt;p&gt;If this is a new area for you then hopefully you'll gain some knowledge from reading this article.  Or perhaps you have been using the same object mapper for some time; should you consider an alternative?&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;What is an object mapper and what problem does it solve?&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Object to object mapping is the process of assigning values from one object to another.  This is common in a layered architectures, particularly those developed using an ORM framework such as Entity Framework Core.  There can be many instances in application code where there is a requirement to translate data between the underlying entity model classes and data transfer objects (DTOs).  Another case where object mapping is used is between DTOs, typically from a Web API to view model classes in MVC web application. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap1_637569511523691537.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap1_637569511523691537.png" alt="Entity model class and DTO class definitions" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mapping code is quite mundane and repetitive to write and a well adopted solution is to use a mapping library.  Mapping libraries generally work by matching members in objects and through object graphs by using a set of conventions based on name and in some cases type too.&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;Benefits of using an Object Mapper&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Solving the object to object mapping problem with a mapping library will make your life as a developer easier. Here are some direct benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not writing repetitive boring code&lt;/li&gt;
&lt;li&gt;Smaller, cleaner codebase&lt;/li&gt;
&lt;li&gt;Consistent naming of properties across codebase&lt;/li&gt;
&lt;li&gt;It is a strategy which is well adopted by developers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;strong&gt;Comparing Object Mapping Libraries&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;In this post each of the mapping libraries are compared for the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Popularity&lt;/li&gt;
&lt;li&gt;Ease of use&lt;/li&gt;
&lt;li&gt;Ease of configuration&lt;/li&gt;
&lt;li&gt;Collections&lt;/li&gt;
&lt;li&gt;Mapping and reverse mapping&lt;/li&gt;
&lt;li&gt;Flattening and unflattening&lt;/li&gt;
&lt;li&gt;Nested objects&lt;/li&gt;
&lt;li&gt;Projections&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this evaluation, a C# .Net Core 5 console application was used using EntityFramework Core and SqlLite database.  It is a simplified model of a layered architecture and &lt;a title="Mapping demo project on GitHub" href="https://github.com/JD-Innovensa/MappingDemo" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The project contains a simple &lt;em&gt;service&lt;/em&gt; interface which provides a few basic scenarios to test such as a single-level object  to object mapping, a two-level object mapping with an object containing a collection and a reverse mapping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap2_637569511524698766.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap2_637569511524698766.png" alt="IBookService interface definition from Mapping Demo" width="513" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the project code, the interface is implemented for each mapping solution.  Also for good measure, the interface was implemented without using a mapper. This is the mundane code that we wish to remove by using a mapping library!&lt;/p&gt;

&lt;p&gt;Mapping can get quite in-depth so not all use cases are covered, but this simple project is available and can be further extended to try out different things.&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;Object Mapping Libraries&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Three object mapping libraries were compared: AutoMapper, AgileMapper and Mapster.  There are a range of libraries to use, but here we have a very popular, quite popular and less well-known object mapping library for comparison.&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;AutoMapper&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;This is our current object mapper of choice. We decided to use AutoMapper on some recent projects due to its existing popularity and its use in Jon P Smith’s practical book &lt;a title="Entity Framework Core in Action" href="https://www.thereformedprogrammer.net/" rel="noopener noreferrer"&gt;Entity Framework Core in Action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AutoMapper takes a low-configuration approach and is designed to be used in dependency injection scenarios such as in ASP.Net Core applications.&lt;/p&gt;

&lt;p&gt;AutoMapper demands a configuration which can be divided into profiles and validated.  The library can perform a scanning operation to include all profiles when the configuration is built.  It creates an expression tree which can be inspected during debugging using an extension to view.&lt;/p&gt;

&lt;p&gt;See: &lt;a title="AutoMapper" href="https://automapper.org/" rel="noopener noreferrer"&gt;https://automapper.org/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;AgileMapper&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;AgileMapper is a zero-configuration, highly-configurable object-object mapper with viewable execution plans.&lt;/p&gt;

&lt;p&gt;Members are matched on a set of name and type rules.  If the type doesn’t map, no extra config is required to prevent errors.&lt;/p&gt;

&lt;p&gt;Execution plans are formed by creating an expression tree and compiling to a function, these are then cached.  These plans can be cached upfront and also viewed. The execution plans can be validated during development.&lt;/p&gt;

&lt;p&gt;The library also offers deep cloning, updating and merging amongst other useful features.&lt;/p&gt;

&lt;p&gt;See &lt;a title="AgileMapper" href="https://github.com/agileobjects/AgileMapper" rel="noopener noreferrer"&gt;https://github.com/agileobjects/AgileMapper&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Mapster&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Mapster is another zero-configuration object-object mapper that matches by name.  Once more, it supports configuration that configuration can be validated.&lt;/p&gt;

&lt;p&gt;It uses an extension method which is available on any object to perform the mapping operation.  Another extension method provides a convenient way of passing in values at runtime during mapping.&lt;/p&gt;

&lt;p&gt;Mapster boasts good performance according to its own GitHub project page and compares itself with other libraries favourably. &lt;/p&gt;

&lt;p&gt;The code repo also provides a tool to create DTO classes and extension methods from entity classes which can generate code and save time.&lt;/p&gt;

&lt;p&gt;&lt;a title="Mapster" href="https://github.com/MapsterMapper/Mapster" rel="noopener noreferrer"&gt;https://github.com/MapsterMapper/Mapster&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;How Do They Compare&lt;/strong&gt;&lt;/h2&gt;

&lt;h3&gt;&lt;strong&gt;Popularity&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;In terms of current usage AutoMapper is by far the most popular and established mapping library from the three selected.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;AgileMapper&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;AutoMapper&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Mapster&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;NuGet Version&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;1.8.0&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;10.1.1&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;7.2.0&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;Total Downloads&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;300k&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;150M&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;2.3M&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;Daily Average Downloads&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;100&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;39k&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;1k&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;GitHub Stars&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;375&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;8k&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;1.5k&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* correct as of 2021-05-17&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Ease of use&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;All three were simple to understand and to setup using NuGet.  In terms of usability, Agile Mapper and Mapster were easier than AutoMapper as neither have a requirement for configuration.   &lt;/p&gt;

&lt;p&gt;Getting started is similar for all three libraries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install package&lt;/li&gt;
&lt;li&gt;Have source and destination types to map&lt;/li&gt;
&lt;li&gt;Create configuration (required for AutoMapper)&lt;/li&gt;
&lt;li&gt;Create instance (required for AutoMapper)&lt;/li&gt;
&lt;li&gt;Perform some mapping!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agile Mapper and Mapster both offer a static API, something that AutoMapper removed in v9.&lt;/p&gt;

&lt;p&gt;Mapster is slightly different to the other two as it provides an extension method for the mapping operation. This is a convenient feature and feels natural to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap3_637569519320307972.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap3_637569519320307972.png" alt="Basic use of each object mapper" width="551" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Ease of configuration&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Despite working by convention, mapping configuration is required in most cases. A trivial example included in the code example is to map a source first name and last name to a single destination full name field.  This was a straightforward exercise in all three using fluent syntax.&lt;/p&gt;

&lt;p&gt;All libraries are designed to work with dependency injection with configuration that is created once as a start-up operation.  This will provide the application with a compiled set of mappings leading to a performance gain.&lt;/p&gt;

&lt;p&gt;Configuration can be easily loaded as all the libraries provide a way of scanning for configuration classes, which is especially useful in larger codebases.&lt;/p&gt;

&lt;p&gt;All three support operations before and after mapping takes place.  AgileMapper offers some additional extension methods to limit when this should be applied which is a nice feature.&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Collections&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;AgileMapper seems to support more collection types than AutoMapper or Mapster.  Both AgileMapper and Mapster also have Dictionary support too, but only for keys with a string type. &lt;/p&gt;

&lt;p&gt;During the mapping process, both AutoMapper and AgileMapper initialised empty collections when no value was to be mapped.   This behaviour is good practice as collections are expected to be initialised. Mapster didn’t do this and returned nulls as default behaviour.&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Mapping and reverse mapping&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;All three libraries performed straightforward mapping based on matching names.  They also performed reverse mapping, although AutoMapper required configuration for this to work.&lt;/p&gt;

&lt;p&gt;Another test was to see the default behaviour for a mapping of a larger type into a smaller type, in  this case an int to a byte.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap4_637569519321132640.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap4_637569519321132640.png" alt="Difference between types of members with same name" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three libraries handled the situation differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AutoMapper threw a runtime exception, so this mapping had to be explicitly ignored.&lt;/li&gt;
&lt;li&gt;AgileMapper ignored the mapping as it has more complex mapping rules.&lt;/li&gt;
&lt;li&gt;Mapster actually performed a mapping and added an incorrect value to the destination member. This is easily preventable using the config to ignore the member.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with everything, testing and exploring what is actually happening to data and validating configuration is essential.&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Flattening&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;All three support flattening by convention without any special configuration when the destination names followed a simple pattern.&lt;/p&gt;

&lt;p&gt;For example:  &lt;code&gt;source.Author.StarRating&lt;/code&gt; will map to &lt;code&gt;dest.AuthorStarRating.&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Nested objects&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;All three supported mapping an object graph without any additional configuration.  AgileMapper has a dedicated Flattening API with some useful functions such as flattening an object to a dictionary or query string parameter list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IQueryable Projections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All three support IQueryable projections.  This is a convenient way to map an entity object directly to the destination DTO when working with EF Core.&lt;/p&gt;

&lt;p&gt;They are all very similar except for AutoMapper which requires the config to be passed into the extension method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap5_637569528648900897.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap5_637569528648900897.png" alt="Projection usage for each object mapper" width="400" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The projection ensured only the required members were declared in the select statement.   The code example includes SQL logging so that it possible to inspect the SQL that was generated. &lt;/p&gt;

&lt;p&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap6_637569528651213672.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fivwebsitestorprod.blob.core.windows.net%2Fiv-website-files%2FMap6_637569528651213672.png" alt="EF Core generated SQL" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Each library has documentation, but AutoMapper and AgileMapper have the clearest and most comprehensive content.  Mapster could improve in this area, but hey, this is open source!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;A Note on Performance&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This study didn’t aim to do a performance comparison.  Mapster boasts of superior performance compared to AutoMapper due to its pre-compilation strategy. However, all the libraries work in a similar way using compiled expression trees which are then cached for future execution.&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;First of all, if you are writing manual object to object mapping code, then start using a mapping library!  This will speed up your development leaving you with fewer lines of code to maintain.  There is plenty of choice, but any of the three in this article are suitable candidates.&lt;/p&gt;

&lt;p&gt;If you are looking for a quick way in, then out of these three AgileMapper gives you a no-fuss, zero-config start and you can start by using its static API.&lt;/p&gt;

&lt;p&gt;After looking at AutoMapper, AgileMapper and Mapster, the conclusion is that they are all fairly similar.  They all contain the key features required for object mapping tasks.  There are subtle usage and configuration differences, but any of them should be fine in most cases.&lt;/p&gt;

&lt;p&gt;AutoMapper’s enforced configuration leads to a good habit to define the configuration upfront, but the lack of a static API removes some versatility available in the other two libraries. &lt;/p&gt;

&lt;p&gt;Mapster’s extension method approach is a natural way to map objects and their performance figures, if true, are a bonus, but there was an issue in the mapping that needed configuration to resolve.&lt;/p&gt;

&lt;p&gt;AgileMapper seems to have a good set of rules for mapping with quality documentation and some useful features not available in the other libraries.&lt;/p&gt;

&lt;p&gt;As mentioned previously, in all cases, take the time to validate and check the mapping configuration and be aware that convention-based mapping can lead to data errors in some cases.&lt;/p&gt;

&lt;p&gt;These libraries are feature rich and this article has not covered everything they can do.  If you want to find out more, then read the docs and feel free to use &lt;a title="Mapping Demo project on GitHub" href="https://github.com/JD-Innovensa/MappingDemo" rel="noopener noreferrer"&gt;the demo project!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks for reading this whistle-stop comparison of object mapping libraries.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>webdev</category>
      <category>dotnet</category>
      <category>efcore</category>
    </item>
  </channel>
</rss>
