برچسب: Tests

  • Clean code tips – Tests | Code4IT


    Tests are as important as production code. Well, they are even more important! So writing them well brings lots of benefits to your projects.

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Clean code principles apply not only to production code but even to tests. Indeed, a test should be even more clean, easy-to-understand, and meaningful than production code.

    In fact, tests not only prevent bugs: they even document your application! New team members should look at tests to understand how a class, a function, or a module works.

    So, every test must have a clear meaning, must have its own raison d’être, and must be written well enough to let the readers understand it without too much fuss.

    In this last article of the Clean Code Series, we’re gonna see some tips to improve your tests.

    If you are interested in more tips about Clean Code, here are the other articles:

    1. names and function arguments
    2. comments and formatting
    3. abstraction and objects
    4. error handling
    5. tests

    Why you should keep tests clean

    As I said before, tests are also meant to document your code: given a specific input or state, they help you understand what the result will be in a deterministic way.

    But, since tests are dependent on the production code, you should adapt them when the production code changes: this means that tests must be clean and flexible enough to let you update them without big issues.

    If your test suite is a mess, even the slightest update in your code will force you to spend a lot of time updating your tests: that’s why you should organize your tests with the same care as your production code.

    Good tests have also a nice side effect: they make your code more flexible. Why? Well, if you have a good test coverage, and all your tests are meaningful, you will be more confident in applying changes and adding new functionalities. Otherwise, when you change your code, you will not be sure not only that the new code works as expected, but that you have not introduced any regression.

    So, having a clean, thorough test suite is crucial for the life of your application.

    How to keep tests clean

    We’ve seen why we should write clean tests. But how should you write them?

    Let’s write a bad test:

    [Test]
    public void CreateTableTest()
    {
        //Arrange
        string tableContent = @"<table>
            <thead>
                <tr>
                    <th>ColA</th>
                    <th>ColB</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Text1A</td>
                    <td>Text1B</td>
                </tr>
                <tr>
                    <td>Text2A</td>
                    <td>Text2B</td>
                </tr>
            </tbody>
        </table>";
    
        var tableInfo = new TableInfo(2);
    
    
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(tableContent);
        var node = doc.DocumentNode.ChildNodes[0];
    
        var part = new TableInfoCreator(node);
    
        var result = part.CreateTableInfo();
    
        tableInfo.SetHeaders(new string[] { "ColA", "ColB" });
        tableInfo.AddRow(new string[] { "Text1A", "Text1B" });
        tableInfo.AddRow(new string[] { "Text2A", "Text2B" });
    
        result.Should().BeEquivalentTo(tableInfo);
    }
    

    This test proves that the CreateTableInfo method of the TableInfoCreator class parses correctly the HTML passed in input and returns a TableInfo object that contains info about rows and headers.

    This is kind of a mess, isn’t it? Let’s improve it.

    Use appropriate test names

    What does CreateTableTest do? How does it help the reader understand what’s going on?

    We need to explicitly say what the tests want to achieve. There are many ways to do it; one of the most used is the Given-When-Then pattern: every method name should express those concepts, possibly in a consistent way.

    I like to use always the same format when naming tests: {Something}_Should_{DoSomething}_When_{Condition}. This format explicitly shows what and why the test exists.

    So, let’s change the name:

    [Test]
    public void CreateTableInfo_Should_CreateTableInfoWithCorrectHeadersAndRows_When_TableIsWellFormed()
    {
        //Arrange
        string tableContent = @"<table>
            <thead>
                <tr>
                    <th>ColA</th>
                    <th>ColB</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Text1A</td>
                    <td>Text1B</td>
                </tr>
                <tr>
                    <td>Text2A</td>
                    <td>Text2B</td>
                </tr>
            </tbody>
        </table>";
    
        var tableInfo = new TableInfo(2);
    
    
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(tableContent);
        HtmlNode node = doc.DocumentNode.ChildNodes[0];
    
        var part = new TableInfoCreator(node);
    
        var result = part.CreateTableInfo();
    
        tableInfo.SetHeaders(new string[] { "ColA", "ColB" });
        tableInfo.AddRow(new string[] { "Text1A", "Text1B" });
        tableInfo.AddRow(new string[] { "Text2A", "Text2B" });
    
        result.Should().BeEquivalentTo(tableInfo);
    }
    

    Now, just by reading the name of the test, we know what to expect.

    Initialization

    The next step is to refactor the tests to initialize all the stuff in a better way.

    The first step is to remove the creation of the HtmlNode seen in the previous example, and move it to an external function: this will reduce code duplication and help the reader understand the test without worrying about the HtmlNode creation details:

    [Test]
    public void CreateTableInfo_Should_CreateTableWithHeadersAndRows_When_TableIsWellFormed()
    {
        //Arrange
        string tableContent = @"<table>
            <thead>
                <tr>
                    <th>ColA</th>
                    <th>ColB</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Text1A</td>
                    <td>Text1B</td>
                </tr>
                <tr>
                    <td>Text2A</td>
                    <td>Text2B</td>
                </tr>
            </tbody>
        </table>";
    
        var tableInfo = new TableInfo(2);
    
     // HERE!
        HtmlNode node = CreateNodeElement(tableContent);
    
        var part = new TableInfoCreator(node);
    
        var result = part.CreateTableInfo();
    
        tableInfo.SetHeaders(new string[] { "ColA", "ColB" });
        tableInfo.AddRow(new string[] { "Text1A", "Text1B" });
        tableInfo.AddRow(new string[] { "Text2A", "Text2B" });
    
        result.Should().BeEquivalentTo(tableInfo);
    }
    
    
    private static HtmlNode CreateNodeElement(string content)
    {
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(content);
        return doc.DocumentNode.ChildNodes[0];
    }
    

    Then, depending on what you are testing, you could even extract input and output creation into different methods.

    If you extract them, you may end up with something like this:

    [Test]
    public void CreateTableInfo_Should_CreateTableWithHeadersAndRows_When_TableIsWellFormed()
    {
        var node = CreateWellFormedHtmlTable();
    
        var part = new TableInfoCreator(node);
    
        var result = part.CreateTableInfo();
    
        TableInfo tableInfo = CreateWellFormedTableInfo();
    
        result.Should().BeEquivalentTo(tableInfo);
    }
    
    private static TableInfo CreateWellFormedTableInfo()
    {
        var tableInfo = new TableInfo(2);
        tableInfo.SetHeaders(new string[] { "ColA", "ColB" });
        tableInfo.AddRow(new string[] { "Text1A", "Text1B" });
        tableInfo.AddRow(new string[] { "Text2A", "Text2B" });
        return tableInfo;
    }
    
    private HtmlNode CreateWellFormedHtmlTable()
    {
        var table = CreateWellFormedTable();
        return CreateNodeElement(table);
    }
    
    private static string CreateWellFormedTable()
        => @"<table>
            <thead>
                <tr>
                    <th>ColA</th>
                    <th>ColA</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Text1A</td>
                    <td>Text1B</td>
                </tr>
                <tr>
                    <td>Text2A</td>
                    <td>Text2B</td>
                </tr>
            </tbody>
        </table>";
    

    So, now, the general structure of the test is definitely better. But, to understand what’s going on, readers have to jump to the details of both CreateWellFormedHtmlTable and CreateWellFormedTableInfo.

    Even worse, you have to duplicate those methods for every test case. You could do a further step by joining the input and the output into a single object:

    
    public class TableTestInfo
    {
        public HtmlNode Html { get; set; }
        public TableInfo ExpectedTableInfo { get; set; }
    }
    
    private TableTestInfo CreateTestInfoForWellFormedTable() =>
    new TableTestInfo
    {
        Html = CreateWellFormedHtmlTable(),
        ExpectedTableInfo = CreateWellFormedTableInfo()
    };
    

    and then, in the test, you simplify everything in this way:

    [Test]
    public void CreateTableInfo_Should_CreateTableWithHeadersAndRows_When_TableIsWellFormed()
    {
        var testTableInfo = CreateTestInfoForWellFormedTable();
    
        var part = new TableInfoCreator(testTableInfo.Html);
    
        var result = part.CreateTableInfo();
    
        TableInfo tableInfo = testTableInfo.ExpectedTableInfo;
    
        result.Should().BeEquivalentTo(tableInfo);
    }
    

    In this way, you have all the info in a centralized place.

    But, sometimes, this is not the best way. Or, at least, in my opinion.

    In the previous example, the most important part is the elaboration of a specific input. So, to help readers, I usually prefer to keep inputs and outputs listed directly in the test method.

    On the contrary, if I had to test for some properties of a class or method (for instance, test that the sorting of an array with repeated values works as expected), I’d extract the initializations outside the test methods.

    AAA: Arrange, Act, Assert

    A good way to write tests is to write them with a structured and consistent template. The most used way is the Arrange-Act-Assert pattern:

    That means that in the first part of the test you set up the objects and variables that will be used; then, you’ll perform the operation under test; finally, you check if the test passes by using assertion (like a simple Assert.IsTrue(condition)).

    I prefer to explicitly write comments to separate the 3 parts of each test, like this:

    [Test]
    public void CreateTableInfo_Should_CreateTableWithHeadersAndRows_When_TableIsWellFormed()
    {
        // Arrange
        var testTableInfo = CreateTestInfoForWellFormedTable();
        TableInfo expectedTableInfo = testTableInfo.ExpectedTableInfo;
    
        var part = new TableInfoCreator(testTableInfo.Html);
    
        // Act
        var actualResult = part.CreateTableInfo();
    
        // Assert
        actualResult.Should().BeEquivalentTo(expectedTableInfo);
    }
    

    Only one assertion per test (with some exceptions)

    Ideally, you may want to write tests with only a single assertion.

    Let’s take as an example a method that builds a User object using the parameters in input:

    public class User
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public Address AddressInfo { get; set; }
    }
    
    public class Address
    {
        public string Country { get; set; }
        public string City { get; set; }
    }
    
    public User BuildUser(string name, string lastName, DateTime birthdate, string country, string city)
    {
        return new User
        {
            FirstName = name,
            LastName = lastName,
            BirthDate = birthdate,
            AddressInfo = new Address
            {
                Country = country,
                City = city
            }
        };
    }
    

    Nothing fancy, right?

    So, ideally, we should write tests with a single assert (ignore in the next examples the test names – I removed the when part!):

    [Test]
    public void BuildUser_Should_CreateUserWithCorrectName()
    {
        // Arrange
        var name = "Davide";
    
        // Act
        var user = BuildUser(name, null, DateTime.Now, null, null);
    
        // Assert
        user.FirstName.Should().Be(name);
    }
    
    [Test]
    public void BuildUser_Should_CreateUserWithCorrectLastName()
    {
        // Arrange
        var lastName = "Bellone";
    
        // Act
        var user = BuildUser(null, lastName, DateTime.Now, null, null);
    
        // Assert
        user.LastName.Should().Be(lastName);
    }
    

    … and so on. Imagine writing a test for each property: your test class will be full of small methods that only clutter the code.

    If you can group assertions in a logical way, you could write more asserts in a single test:

    [Test]
    public void BuildUser_Should_CreateUserWithCorrectPlainInfo()
    {
        // Arrange
        var name = "Davide";
        var lastName = "Bellone";
        var birthDay = new DateTime(1991, 1, 1);
    
        // Act
        var user = BuildUser(name, lastName, birthDay, null, null);
    
        // Assert
        user.FirstName.Should().Be(name);
        user.LastName.Should().Be(lastName);
        user.BirthDate.Should().Be(birthDay);
    }
    

    This is fine because the three properties (FirstName, LastName, and BirthDate) are logically on the same level and with the same meaning.

    One concept per test

    As we stated before, it’s not important to test only one property per test: each and every test must be focused on a single concept.

    By looking at the previous examples, you can notice that the AddressInfo property is built using the values passed as parameters on the BuildUser method. That makes it a good candidate for its own test.

    Another way of seeing this tip is thinking of the properties of an object (I mean, the mathematical properties). If you’re creating your custom sorting, think of which properties can be applied to your method. For instance:

    • an empty list, when sorted, is still an empty list
    • an item with 1 item, when sorted, still has one item
    • applying the sorting to an already sorted list does not change the order

    and so on.

    So you don’t want to test every possible input but focus on the properties of your method.

    In a similar way, think of a method that gives you the number of days between today and a certain date. In this case, just a single test is not enough.

    You have to test – at least – what happens if the other date:

    • is exactly today
    • it is in the future
    • it is in the past
    • it is next year
    • it is February, the 29th of a valid year (to check an odd case)
    • it is February, the 30th (to check an invalid date)

    Each of these tests is against a single value, so you might be tempted to put everything in a single test method. But here you are running tests against different concepts, so place every one of them in a separate test method.

    Of course, in this example, you must not rely on the native way to get the current date (in C#, DateTime.Now or DateTime.UtcNow). Rather, you have to mock the current date.

    FIRST tests: Fast, Independent, Repeatable, Self-validating, and Timed

    You’ll often read the word FIRST when talking about the properties of good tests. What does FIRST mean?

    It is simply an acronym. A test must be Fast, Independent, Repeatable, Self-validating, and Timed.

    Fast

    Tests should be fast. How much? Enough to don’t discourage the developers to run them. This property applies only to Unit Tests: in fact, while each test should run in less than 1 second, you may have some Integration and E2E tests that take more than 10 seconds – it depends on what you’re testing.

    Now, imagine if you have to update one class (or one method), and you have to re-run all your tests. If the whole tests suite takes just a few seconds, you can run them whenever you want – some devs run all the tests every time they hit Save; if every single test takes 1 second to run, and you have 200 tests, just a simple update to one class makes you lose at least 200 seconds: more than 3 minutes. Yes, I know that you can run them in parallel, but that’s not the point!

    So, keep your tests short and fast.

    Independent

    Every test method must be independent of the other tests.

    This means that the result and the execution of one method must not impact the execution of another one. Conversely, one method must not rely on the execution of another method.

    A concrete example?

    public class MyTests
    {
        string userName = "Lenny";
    
        [Test]
        public void Test1()
        {
            Assert.AreEqual("Lenny", userName);
            userName = "Carl";
    
        }
    
        [Test]
        public void Test2()
        {
            Assert.AreEqual("Carl", userName);
        }
    
    }
    

    Those tests are perfectly valid if run in sequence. But Test1 affects the execution of Test2 by setting a global variable
    used by the second method. But what happens if you run only Test2? It will fail. Same result if the tests are run in a different order.

    So, you can transform the previous method in this way:

    public class MyTests
    {
        string userName;
    
        [SetUp]
        public void Setup()
        {
            userName = "Boe";
        }
    
        [Test]
        public void Test1()
        {
            userName = "Lenny";
            Assert.AreEqual("Lenny", userName);
    
        }
    
        [Test]
        public void Test2()
        {
            userName = "Carl";
            Assert.AreEqual("Carl", userName);
        }
    
    }
    

    In this way, we have a default value, Boe, that gets overridden by the single methods – only when needed.

    Repeatable

    Every Unit test must be repeatable: this means that you must be able to run them at any moment and on every machine (and get always the same result).

    So, avoid all the strong dependencies on your machine (like file names, absolute paths, and so on), and everything that is not directly under your control: the current date and time, random-generated numbers, and GUIDs.

    To work with them there’s only a solution: abstract them and use a mocking mechanism.

    If you want to learn 3 ways to do this, check out my 3 ways to inject DateTime and test it. There I explained how to inject DateTime, but the same approaches work even for GUIDs and random numbers.

    Self-validating

    You must be able to see the result of a test without performing more actions by yourself.

    So, don’t write your test results on an external file or source, and don’t put breakpoints on your tests to see if they’ve passed.

    Just put meaningful assertions and let your framework (and IDE) tell you the result.

    Timely

    You must write your tests when required. Usually, when using TDD, you write your tests right before your production code.

    So, this particular property applies only to devs who use TDD.

    Wrapping up

    In this article, we’ve seen that even if many developers consider tests redundant and not worthy of attention, they are first-class citizens of our applications.

    Paying enough attention to tests brings us a lot of advantages:

    • tests document our code, thus helping onboarding new developers
    • they help us deploy with confidence a new version of our product, without worrying about regressions
    • they prove that our code has no bugs (well, actually you’ll always have a few bugs, it’s just that you haven’t discovered them yet )
    • code becomes more flexible and can be extended without too many worries

    So, write meaningful tests, and always well written.

    Quality over quantity, always!

    Happy coding!



    Source link

  • Tests should be even more well-written than production code &vert; Code4IT

    Tests should be even more well-written than production code | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    You surely take care of your code to make it easy to read and understand, right? RIGHT??

    Well done! 👏

    But most of the developers tend to write good production code (the one actually executed by your system), but very poor test code.

    Production code is meant to be run, while tests are also meant to document your code; therefore there must not be doubts about the meaning and the reason behind a test.
    This also means that all the names must be explicit enough to help readers understand how and why a test should pass.

    This is a valid C# test:

    [Test]
    public void TestHtmlParser()
    {
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml("<p>Hello</p>");
        var node = doc.DocumentNode.ChildNodes[0];
        var parser = new HtmlParser();
    
        Assert.AreEqual("Hello", parser.ParseContent(node));
    }
    

    What is the meaning of this test? We should be able to understand it just by reading the method name.

    Also, notice that here we are creating the HtmlNode object; imagine if this node creation is present in every test method: you will see the same lines of code over and over again.

    Thus, we can refactor this test in this way:

     [Test]
    public void HtmlParser_ExtractsContent_WhenHtmlIsParagraph()
    {
        //Arrange
        string paragraphContent = "Hello";
        string htmlParagraph = $"<p>{paragraphContent}</p>";
        HtmlNode htmlNode = CreateHtmlNode(htmlParagraph);
        var htmlParser = new HtmlParser();
    
        //Act
        var parsedContent = htmlParser.ParseContent(htmlNode);
    
        //Assert
        Assert.AreEqual(paragraphContent, parsedContent);
    }
    

    This test is definitely better:

    • you can understand its meaning by reading the test name
    • the code is concise, and some creation parts are refactored out
    • we’ve well separated the 3 parts of the tests: Arrange, Act, Assert (we’ve already talked about it here)

    Wrapping up

    Tests are still part of your project, even though they are not used directly by your customers.

    Never skip tests, and never write them in a rush. After all, when you encounter a bug, the first thing you should do is write a test to reproduce the bug, and then validate the fix using that same test.

    So, keep writing good code, for tests too!

    Happy coding!

    🐧



    Source link

  • F.I.R.S.T. acronym for better unit tests &vert; Code4IT

    F.I.R.S.T. acronym for better unit tests | Code4IT


    Good unit tests have some properties in common: they are Fast, Independent, Repeatable, Self-validating, and Thorough. In a word: FIRST!

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    FIRST is an acronym that you should always remember if you want to write clean and extensible tests.

    This acronym tells us that Unit Tests should be Fast, Independent, Repeatable, Self-validating, and Thorough.

    Fast

    You should not create tests that require a long time for setup and start-up: ideally, you should be able to run the whole test suite in under a minute.

    If your unit tests are taking too much time for running, there must be something wrong with it; there are many possibilities:

    1. You’re trying to access remote sources (such as real APIs, Databases, and so on): you should mock those dependencies to make tests faster and to avoid accessing real resources. If you need real data, consider creating integration/e2e tests instead.
    2. Your system under test is too complex to build: too many dependencies? DIT value too high?
    3. The method under test does too many things. You should consider splitting it into separate, independent methods, and let the caller orchestrate the method invocations as necessary.

    Independent (or Isolated)

    Test methods should be independent of one another.

    Avoid doing something like this:

    MyObject myObj = null;
    
    [Fact]
    void Test1()
    {
        myObj = new MyObject();
        Assert.True(string.IsNullOrEmpty(myObj.MyProperty));
    
    }
    
    [Fact]
    void Test2()
    {
    
        myObj.MyProperty = "ciao";
        Assert.Equal("oaic", Reverse(myObj.MyProperty));
    
    }
    

    Here, to have Test2 working correctly, Test1 must run before it, otherwise myObj would be null. There’s a dependency between Test1 and Test2.

    How to avoid it? Create new instances for every test! May it be with some custom methods or in the StartUp phase. And remember to reset the mocks as well.

    Repeatable

    Unit Tests should be repeatable. This means that wherever and whenever you run them, they should behave correctly.

    So you should remove any dependency on the file system, current date, and so on.

    Take this test as an example:

    [Fact]
    void TestDate_DoNotDoIt()
    {
    
        DateTime d = DateTime.UtcNow;
        string dateAsString = d.ToString("yyyy-MM-dd");
    
        Assert.Equal("2022-07-19", dateAsString);
    }
    

    This test is strictly bound to the current date. So, if I’ll run this test again in a month, it will fail.

    We should instead remove that dependency and use dummy values or mock.

    [Fact]
    void TestDate_DoIt()
    {
    
        DateTime d = new DateTime(2022,7,19);
        string dateAsString = d.ToString("yyyy-MM-dd");
    
        Assert.Equal("2022-07-19", dateAsString);
    }
    

    There are many ways to inject DateTime (and other similar dependencies) with .NET. I’ve listed some of them in this article: “3 ways to inject DateTime and test it”.

    Self-validating

    Self-validating means that a test should perform operations and programmatically check for the result.

    For instance, if you’re testing that you’ve written something on a file, the test itself is in charge of checking that it worked correctly. No manual operations should be done.

    Also, tests should provide explicit feedback: a test either passes or fails; no in-between.

    Thorough

    Unit Tests should be thorough in that they must validate both the happy paths and the failing paths.

    So you should test your functions with valid inputs and with invalid inputs.

    You should also validate what happens if an exception is thrown while executing the path: are you handling errors correctly?

    Have a look at this class, with a single, simple, method:

    public class ItemsService
    {
    
        readonly IItemsRepository _itemsRepo;
    
        public ItemsService(IItemsRepository itemsRepo)
        {
            _itemsRepo = itemsRepo;
        }
    
        public IEnumerable<Item> GetItemsByCategory(string category, int maxItems)
        {
    
            var allItems = _itemsRepo.GetItems();
    
            return allItems
                    .Where(i => i.Category == category)
                    .Take(maxItems);
        }
    }
    

    Which tests should you write for GetItemsByCategory?

    I can think of these:

    • what if category is null or empty?
    • what if maxItems is less than 0?
    • what if allItems is null?
    • what if one of the items inside allItems is null?
    • what if _itemsRepo.GetItems() throws an exception?
    • what if _itemsRepo is null?

    As you can see, even for a trivial method like this you should write a lot of tests, to ensure that you haven’t missed anything.

    Conclusion

    F.I.R.S.T. is a good way to way to remember the properties of a good unit test suite.

    Always try to stick to it, and remember that tests should be written even better than production code.

    Happy coding!

    🐧



    Source link

  • Use custom Equality comparers in Nunit tests &vert; Code4IT

    Use custom Equality comparers in Nunit tests | Code4IT


    When writing unit tests, there are smarter ways to check if two objects are equal than just comparing every field one by one.

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    When writing unit tests, you might want to check that the result returned by a method is equal to the one you’re expecting.

    [Test]
    public void Reverse_Should_BeCorrect()
    {
      string input = "hello";
      string result = MyUtils.Reverse(input);
    
      Assert.That(result, Is.EqualTo("olleh"));
    }
    

    This approach works pretty fine unless you want to check values on complex types with no equality checks.

    public class Player
    {
      public int Id { get; set; }
      public string UserName { get; set; }
      public int Score { get; set; }
    }
    

    Let’s create a dummy method that clones a player:

    public static Player GetClone(Player source)
      => new Player
        {
          Id = source.Id,
          UserName = source.UserName,
          Score = source.Score
        };
    

    and call it this way:

    [Test]
    public void GetClone()
    {
      var originalPlayer = new Player { Id = 1, UserName = "me", Score = 1 };
    
      var clonedPlayer = MyUtils.GetClone(originalPlayer);
    
      Assert.That(clonedPlayer, Is.EqualTo(originalPlayer));
    }
    

    Even though logically originalPlayer and clonedPlayer are equal, they are not the same: the test will fail!

    Lucky for us, we can specify the comparison rules!

    Equality function: great for simple checks

    Say that we don’t want to check that all the values match. We only care about Id and UserName.

    When we have just a few fields to check, we can use a function to specify that two items are equal:

    [Test]
    public void GetClone_WithEqualityFunction()
    {
      var originalPlayer = new Player { Id = 1, UserName = "me", Score = 1 };
    
      var clonedPlayer = MyUtils.GetClone(originalPlayer);
    
      Assert.That(clonedPlayer, Is.EqualTo(originalPlayer).Using<Player>(
        (Player a, Player b) => a.Id == b.Id && a.UserName == b.UserName)
        );
    }
    

    Clearly, if the method becomes unreadable, you can refactor the comparer function as so:

    [Test]
    public void GetClone_WithEqualityFunction()
    {
      var originalPlayer = new Player { Id = 1, UserName = "me", Score = 1 };
    
      var clonedPlayer = MyUtils.GetClone(originalPlayer);
    
      Func<Player, Player, bool> comparer = (Player a, Player b) => a.Id == b.Id && a.UserName == b.UserName;
    
      Assert.That(clonedPlayer, Is.EqualTo(originalPlayer).Using<Player>(comparer));
    }
    

    EqualityComparer class: best for complex scenarios

    If you have a complex scenario to validate, you can create a custom class that implements the IEqualityComparer interface. Here, you have to implement two methods: Equals and GetHashCode.

    Instead of just implementing the same check inside the Equals method, we’re gonna try a different approach: we’re gonna use GetHashCode to determine how to identify a Player, by generating a string used as a simple identifier, and then we’re gonna use the HashCode of the result string for the actual comparison:

    public class PlayersComparer : IEqualityComparer<Player>
    {
        public bool Equals(Player? x, Player? y)
        {
            return
                (x is null && y is null)
                ||
                GetHashCode(x) == GetHashCode(y);
        }
    
        public int GetHashCode([DisallowNull] Player obj)
        {
            return $"{obj.Id}-{obj.UserName}".GetHashCode();
        }
    }
    

    Clearly, I’ve also added a check on nullability: (x is null && y is null).

    Now we can instantiate a new instance of PlayersComparer and use it to check whether two players are equivalent:

    [Test]
    public void GetClone_WithEqualityComparer()
    {
        var originalPlayer = new Player { Id = 1, UserName = "me", Score = 1 };
    
        var clonedPlayer = MyUtils.GetClone(originalPlayer);
    
        Assert.That(clonedPlayer, Is.EqualTo(originalPlayer).Using<Player>(new PlayersComparer()));
    }
    

    Of course, you can customize the Equals method to use whichever condition to validate the equivalence of two instances, depending on your business rules. For example, you can say that two vectors are equal if they have the exact same length and direction, even though the start and end points are different.

    ❓ A question for you: where would you put the equality check: in the production code or in the tests project?

    Wrapping up

    As we’ve learned in this article, there are smarter ways to check if two objects are equal than just comparing every field one by one.

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • Advanced Integration Tests for .NET 7 API with WebApplicationFactory and NUnit &vert; Code4IT

    Advanced Integration Tests for .NET 7 API with WebApplicationFactory and NUnit | Code4IT


    Integration Tests are incredibly useful: a few Integration Tests are often more useful than lots of Unit Tests. Let’s learn some advanced capabilities of WebApplicationFactory.

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    In a previous article, we learned a quick way to create Integration Tests for ASP.NET API by using WebApplicationFactory. That was a nice introductory article. But now we will delve into more complex topics and examples.

    In my opinion, a few Integration Tests and just the necessary number of Unit tests are better than hundreds of Unit Tests and no Integration Tests at all. In general, the Testing Diamond should be preferred over the Testing Pyramid (well, in most cases).

    In this article, we are going to create advanced Integration Tests by defining custom application settings, customizing dependencies to be used only during tests, defining custom logging, and performing complex operations in our tests.

    For the sake of this article, I created a sample API application that exposes one single endpoint whose purpose is to retrieve some info about the URL passed in the query string. For example,

    GET /SocialPostLink?uri=https%3A%2F%2Ftwitter.com%2FBelloneDavide%2Fstatus%2F1682305491785973760
    

    will return

    {
      "instanceName": "Real",
      "info": {
        "socialNetworkName": "Twitter",
        "sourceUrl": "https://twitter.com/BelloneDavide/status/1682305491785973760",
        "username": "BelloneDavide",
        "id": "1682305491785973760"
      }
    }
    

    For completeness, instanceName is a value coming from the appsettings.json file, while info is an object that holds some info about the social post URL passed as input.

    Internally, the code is using the Chain of Responsibility pattern: there is a handler that “knows” if it can handle a specific URL; if so, it just elaborates the input; otherwise, it calls the next handler.

    There is also a Factory that builds the chain, and finally, a Service that instantiates the Factory and then resolves the dependencies.

    As you can see, this solution can become complex. We could run lots of Unit Tests to validate that the Chain of Responsibility works as expected. We can even write a Unit Tests suite for the Factory.

    Class Diagram

    But, at the end of the day, we don’t really care about the internal structure of the project: as long as it works as expected, we could even use a huge switch block (clearly, with all the consequences of this choice). So, let’s write some Integration Tests.

    How to create a custom WebApplicationFactory in .NET

    When creating Integration Tests for .NET APIs you have to instantiate a new instance of WebApplicationFactory, a class coming from the Microsoft.AspNetCore.Mvc.Testing NuGet Package.

    Since we are going to define it once and reuse it across all the tests, let’s create a new class that extends WebApplicationFactory, and add some custom behavior to it.

    public class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>
    {
    
    }
    

    Let’s focus on the Program class: as you can see, the WebApplicationFactory class requires an entry point. Generally speaking, it’s the Program class of our application.

    If you hover on WebApplicationFactory<Program> and hit CTRL+. on Visual Studio, the autocomplete proposes two alternatives: one is the Program class defined in your APIs, while the other one is the Program class defined in Microsoft.VisualStudio.TestPlatform.TestHost. Choose the one for your API application! The WebApplicationFactory class will then instantiate your API following the instructions defined in your Program class, thus resolving all the dependencies and configurations as if you were running your application locally.

    What to do if you don’t have the Program class? If you use top-level statements, you don’t have the Program class, because it’s “implicit”. So you cannot reference the whole class. Unless… You have to create a new partial class named Program, and leave it empty: this way, you have a class name that can be used to reference the API definition:

    public partial class Program { }
    

    Here you can override some definitions of the WebHost to be created by calling ConfigureWebHost:

    public class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
              builder.ConfigureAppConfiguration((host, configurationBuilder) => { });
        }
    }
    

    How to use WebApplicationFactory in your NUnit tests

    It’s time to start working on some real Integration Tests!

    As we said before, we have only one HTTP endpoint, defined like this:

    
    private readonly ISocialLinkParser _parser;
    private readonly ILogger<SocialPostLinkController> _logger;
    private readonly IConfiguration _config;
    
    public SocialPostLinkController(ISocialLinkParser parser, ILogger<SocialPostLinkController> logger, IConfiguration config)
    {
        _parser = parser;
        _logger = logger;
        _config = config;
    }
    
    [HttpGet]
    public IActionResult Get([FromQuery] string uri)
    {
        _logger.LogInformation("Received uri {Uri}", uri);
        if (Uri.TryCreate(uri, new UriCreationOptions {  }, out Uri _uri))
        {
            var linkInfo = _parser.GetLinkInfo(_uri);
            _logger.LogInformation("Uri {Uri} is of type {Type}", uri, linkInfo.SocialNetworkName);
    
            var instance = new Instance
            {
                InstanceName = _config.GetValue<string>("InstanceName"),
                Info = linkInfo
            };
            return Ok(instance);
        }
        else
        {
            _logger.LogWarning("Uri {Uri} is not a valid Uri", uri);
            return BadRequest();
        }
    }
    

    We have 2 flows to validate:

    • If the input URI is valid, the HTTP Status code should be 200;
    • If the input URI is invalid, the HTTP Status code should be 400;

    We could simply write Unit Tests for this purpose, but let me write Integration Tests instead.

    First of all, we have to create a test class and create a new instance of IntegrationTestWebApplicationFactory. Then, we will create a new HttpClient every time a test is run that will automatically include all the services and configurations defined in the API application.

    public class ApiIntegrationTests : IDisposable
    {
        private IntegrationTestWebApplicationFactory _factory;
        private HttpClient _client;
    
        [OneTimeSetUp]
        public void OneTimeSetup() => _factory = new IntegrationTestWebApplicationFactory();
    
        [SetUp]
        public void Setup() => _client = _factory.CreateClient();
    
        public void Dispose() => _factory?.Dispose();
    }
    

    As you can see, the test class implements IDisposable so that we can call Dispose() on the IntegrationTestWebApplicationFactory instance.

    From now on, we can use the _client instance to work with the in-memory instance of the API.

    One of the best parts of it is that, since it’s an in-memory instance, we can even debug our API application. When you create a test and put a breakpoint in the production code, you can hit it and see the actual values as if you were running the application in a browser.

    Now that we have the instance of HttpClient, we can create two tests to ensure that the two cases we defined before are valid. If the input string is a valid URI, return 200:

    [Test]
    public async Task Should_ReturnHttp200_When_UrlIsValid()
    {
        string inputUrl = "https://twitter.com/BelloneDavide/status/1682305491785973760";
    
        var result = await _client.GetAsync($"SocialPostLink?uri={inputUrl}");
    
        Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    }
    

    Otherwise, return Bad Request:

    [Test]
    public async Task Should_ReturnBadRequest_When_UrlIsNotValid()
    {
        string inputUrl = "invalid-url";
    
        var result = await _client.GetAsync($"/SocialPostLink?uri={inputUrl}");
    
        Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
    }
    

    How to create test-specific configurations using InMemoryCollection

    WebApplicationFactory is highly configurable thanks to the ConfigureWebHost method. For instance, you can customize the settings injected into your services.

    Usually, you want to rely on the exact same configurations defined in your appsettings.json file to ensure that the system behaves correctly with the “real” configurations.

    For example, I defined the key “InstanceName” in the appsettings.json file whose value is “Real”, and whose value is used to create the returned Instance object. We can validate that that value is being read from that source as validated thanks to this test:

    [Test]
    public async Task Should_ReadInstanceNameFromSettings()
    {
        string inputUrl = "https://twitter.com/BelloneDavide/status/1682305491785973760";
    
        var result = await _client.GetFromJsonAsync<Instance>($"/SocialPostLink?uri={inputUrl}");
    
        Assert.That(result.InstanceName, Is.EqualTo("Real"));
    }
    

    But some other times you might want to override a specific configuration key.

    The ConfigureAppConfiguration method allows you to customize how you manage Configurations by adding or removing sources.

    If you want to add some configurations specific to the WebApplicationFactory, you can use AddInMemoryCollection, a method that allows you to add configurations in a key-value format:

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((host, configurationBuilder) =>
        {
            configurationBuilder.AddInMemoryCollection(
                new List<KeyValuePair<string, string?>>
                {
                    new KeyValuePair<string, string?>("InstanceName", "FromTests")
                });
        });
    }
    

    Even if you had the InstanceName configured in your appsettings.json file, the value is now overridden and set to FromTests.

    You can validate this change by simply replacing the expected value in the previous test:

    [Test]
    public async Task Should_ReadInstanceNameFromSettings()
    {
        string inputUrl = "https://twitter.com/BelloneDavide/status/1682305491785973760";
    
        var result = await _client.GetFromJsonAsync<Instance>($"/SocialPostLink?uri={inputUrl}");
    
        Assert.That(result.InstanceName, Is.EqualTo("FromTests"));
    }
    

    If you also want to discard all the other existing configuration sources, you can call configurationBuilder.Sources.Clear() before AddInMemoryCollection and remove all the other existing configurations.

    How to set up custom dependencies for your tests

    Maybe you don’t want to resolve all the existing dependencies, but just a subset of them. For example, you might not want to call external APIs with a limited number of free API calls to avoid paying for the test-related calls. You can then rely on Stub classes that simulate the dependency by giving you full control of the behavior.

    We want to replace an existing class with a Stub one: we are going to create a stub class that will be used instead of SocialLinkParser:

    public class StubSocialLinkParser : ISocialLinkParser
    {
        public LinkInfo GetLinkInfo(Uri postUri) => new LinkInfo
        {
            SocialNetworkName = "test from stub",
            Id = "test id",
            SourceUrl = postUri,
            Username = "test username"
        };
    }
    

    We can then customize Dependency Injection to use StubSocialLinkParser in place of SocialLinkParser by specifying the dependency within the ConfigureTestServices method:

    builder.ConfigureTestServices(services =>
    {
        services.AddScoped<ISocialLinkParser, StubSocialLinkParser>();
    });
    

    Finally, we can create a method to validate this change:

    [Test]
    public async Task Should_UseStubName()
    {
        string inputUrl = "https://twitter.com/BelloneDavide/status/1682305491785973760";
    
        var result = await _client.GetFromJsonAsync<Instance>($"/SocialPostLink?uri={inputUrl}");
    
        Assert.That(result.Info.SocialNetworkName, Is.EqualTo("test from stub"));
    }
    

    How to create Integration Tests on specific resolved dependencies

    Now we are going to test that the SocialLinkParser does its job, regardless of the internal implementation. Right now we have used the Chain of Responsibility pattern, and we rely on the ISocialLinksFactory interface to create the correct sequence of handlers. But we don’t know in the future how we will define the code: maybe we will replace it all with a huge if-else sequence – the most important part is that the code works, regardless of the internal implementation.

    We can proceed in two ways: writing tests on the interface or writing tests on the concrete class.

    For the sake of this article, we are going to run tests on the SocialLinkParser class. Not the interface, but the concrete class. The first step is to add the class to the DI engine in the Program class:

    builder.Services.AddScoped<SocialLinkParser>();
    

    Now we can create a test to validate that it is working:

    [Test]
    public async Task Should_ResolveDependency()
    {
        using (var _scope = _factory.Services.CreateScope())
        {
            var service = _scope.ServiceProvider.GetRequiredService<SocialLinkParser>();
            Assert.That(service, Is.Not.Null);
            Assert.That(service, Is.AssignableTo<SocialLinkParser>());
        }
    }
    

    As you can see, we are creating an IServiceScope by calling _factory.Services.CreateScope(). Since we have to discard this scope after the test run, we have to place it within a using block. Then, we can create a new instance of SocialLinkParser by calling _scope.ServiceProvider.GetRequiredService<SocialLinkParser>() and create all the tests we want on the concrete implementation of the class.

    The benefit of this approach is that you have all the internal dependencies already resolved, without relying on mocks. You can then ensure that everything, from that point on, works as you expect.

    Here I created the scope within a using block. There is another approach that I prefer: create the scope instance in the SetUp method, and call Dispose() on it the the TearDown phase:

    protected IServiceScope _scope;
    protected SocialLinkParser _sut;
    private IntegrationTestWebApplicationFactory _factory;
    
    [OneTimeSetUp]
    public void OneTimeSetup() => _factory = new IntegrationTestWebApplicationFactory();
    
    [SetUp]
    public void Setup()
    {
        _scope = _factory.Services.CreateScope();
        _sut = _scope.ServiceProvider.GetRequiredService<SocialLinkParser>();
    }
    
    [TearDown]
    public void TearDown()
    {
        _sut = null;
        _scope.Dispose();
    }
    
    public void Dispose() => _factory?.Dispose();
    

    You can see an example of the implementation here in the SocialLinkParserTests class.

    Where are my logs?

    Sometimes you just want to see the logs generated by your application to help you debug an issue (yes, you can simply debug the application!). But, unless properly configured, the application logs will not be available to you.

    But you can add logs to the console easily by customizing the adding the Console sink in your ConfigureTestServices method:

    builder.ConfigureTestServices(services =>
    {
        services.AddLogging(builder => builder.AddConsole().AddDebug());
    });
    

    Now you will be able to see all the logs you generated in the Output panel of Visual Studio by selecting the Tests source:

    Logs appear in the Output panel of VisualStudio

    Beware that you are still reading the configurations for logging from the appsettings file! If you have specified in your project to log directly to a sink (such as DataDog or SEQ), your tests will send those logs to the specified sinks. Therefore, you should get rid of all the other logging sources by calling ClearProviders():

    services.AddLogging(builder => builder.ClearProviders() .AddConsole().AddDebug());
    

    Full example

    In this article, we’ve configured many parts of our WebApplicationFactory. Here’s the final result:

    public class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureAppConfiguration((host, configurationBuilder) =>
            {
                // Remove other settings sources, if necessary
                configurationBuilder.Sources.Clear();
    
                //Create custom key-value pairs to be used as settings
                configurationBuilder.AddInMemoryCollection(
                    new List<KeyValuePair<string, string?>>
                    {
                        new KeyValuePair<string, string?>("InstanceName", "FromTests")
                    });
            });
    
            builder.ConfigureTestServices(services =>
            {
                //Add stub classes
                services.AddScoped<ISocialLinkParser, StubSocialLinkParser>();
    
                //Configure logging
                services.AddLogging(builder => builder.ClearProviders().AddConsole().AddDebug());
            });
        }
    }
    

    You can find the source code used for this article on my GitHub; feel free to download it and toy with it!

    Further readings

    This is an in-depth article about Integration Tests in .NET. I already wrote an article about it with a simpler approach that you might enjoy:

    🔗 How to run Integration Tests for .NET API | Code4IT

    This article first appeared on Code4IT 🐧

    As I often say, a few Integration Tests are often more useful than a ton of Unit Tests. Focusing on Integration Tests instead that on Unit Tests has the benefit of ensuring that the system behaves correctly regardless of the internal implementation.

    In this article, I used the Chain of Responsibility pattern, so Unit Tests would be tightly coupled to the Handlers. If we decided to move to another pattern, we would have to delete all the existing tests and rewrite everything from scratch.

    Therefore, in my opinion, the Testing Diamond is often more efficient than the Testing Pyramid, as I explained here:

    🔗 Testing Pyramid vs Testing Diamond (and how they affect Code Coverage) | Code4IT

    Wrapping up

    This was a huge article, I know.

    Again, feel free to download and run the example code I shared on my GitHub.

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • How to create Unit Tests for Model Validation | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Model validation is fundamental to any project: it brings security and robustness acting as a first shield against an invalid state.

    You should then add Unit Tests focused on model validation. In fact, when defining the input model, you should always consider both the valid and, even more, the invalid models, making sure that all the invalid models are rejected.

    BDD is a good approach for this scenario, and you can use TDD to implement it gradually.

    Okay, but how can you validate that the models and model attributes you defined are correct?

    Let’s define a simple model:

    public class User
    {
        [Required]
        [MinLength(3)]
        public string FirstName { get; set; }
    
        [Required]
        [MinLength(3)]
        public string LastName { get; set; }
    
        [Range(18, 100)]
        public int Age { get; set; }
    }
    

    Have we defined our model correctly? Are we covering all the edge cases? A well-written Unit Test suite is our best friend here!

    We have two choices: we can write Integration Tests to send requests to our system, which is running an in-memory server, and check the response we receive. Or we can use the internal Validator class, the one used by ASP.NET to validate input models, to create slim and fast Unit Tests. Let’s use the second approach.

    Here’s a utility method we can use in our tests:

    public static IList<ValidationResult> ValidateModel(object model)
    {
        var results = new List<ValidationResult>();
    
        var validationContext = new ValidationContext(model, null, null);
    
        Validator.TryValidateObject(model, validationContext, results, true);
    
        if (model is IValidatableObject validatableModel)
           results.AddRange(validatableModel.Validate(validationContext));
    
        return results;
    }
    

    In short, we create a validation context without any external dependency, focused only on the input model: new ValidationContext(model, null, null).

    Next, we validate each field by calling TryValidateObject and store the validation results in a list, result.

    Finally, if the Model implements the IValidatableObject interface, which exposes the Validate method, we call that Validate() method and store the returned validation errors in the final result list created before.

    As you can see, we can handle both validation coming from attributes on the fields, such as [Required], and custom validation defined in the model class’s Validate() method.

    Now, we can use this method to verify whether the validation passes and, in case it fails, which errors are returned:

    [Test]
    public void User_ShouldPassValidation_WhenModelIsValid()
    {
        var model = new User { FirstName = "Davide", LastName = "Bellone", Age = 32 };
        var validationResult = ModelValidationHelper.ValidateModel(mode);
        Assert.That(validationResult, Is.Empty);
    }
    
    [Test]
    public void User_ShouldNotPassValidation_WhenLastNameIsEmpty()
    {
        var model = new User { FirstName = "Davide", LastName = null, Age = 32 };
        var validationResult = ModelValidationHelper.ValidateModel(mode);
        Assert.That(validationResult, Is.Not.Empty);
    }
    
    
    [Test]
    public void User_ShouldNotPassValidation_WhenAgeIsLessThan18()
    {
        var model = new User { FirstName = "Davide", LastName = "Bellone", Age = 10 };
        var validationResult = ModelValidationHelper.ValidateModel(mode);
        Assert.That(validationResult, Is.Not.Empty);
    }
    

    Further readings

    Model Validation allows you to create more robust APIs. To improve robustness, you can follow Postel’s law:

    🔗 Postel’s law for API Robustness | Code4IT

    This article first appeared on Code4IT 🐧

    Model validation, in my opinion, is one of the cases where Unit Tests are way better than Integration Tests. This is a perfect example of Testing Diamond, the best (in most cases) way to structure a test suite:

    🔗 Testing Pyramid vs Testing Diamond (and how they affect Code Coverage) | Code4IT

    If you still prefer writing Integration Tests for this kind of operation, you can rely on the WebApplicationFactory class and use it in your NUnit tests:

    🔗 Advanced Integration Tests for .NET 7 API with WebApplicationFactory and NUnit | Code4IT

    Wrapping up

    Model validation is crucial. Testing the correctness of model validation can make or break your application. Please don’t skip it!

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • Use TestCase to run similar unit tests with NUnit &vert; Code4IT

    Use TestCase to run similar unit tests with NUnit | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    In my opinion, Unit tests should be well structured and written even better than production code.

    In fact, Unit Tests act as a first level of documentation of what your code does and, if written properly, can be the key to fixing bugs quickly and without adding regressions.

    One way to improve readability is by grouping similar tests that only differ by the initial input but whose behaviour is the same.

    Let’s use a dummy example: some tests on a simple Calculator class that only performs sums on int values.

    public static class Calculator
    {
        public static int Sum(int first, int second) => first + second;
    }
    

    One way to create tests is by creating one test for each possible combination of values:

    public class SumTests
    {
    
        [Test]
        public void SumPositiveNumbers()
        {
            var result = Calculator.Sum(1, 5);
            Assert.That(result, Is.EqualTo(6));
        }
    
        [Test]
        public void SumNegativeNumbers()
        {
            var result = Calculator.Sum(-1, -5);
            Assert.That(result, Is.EqualTo(-6));
        }
    
        [Test]
        public void SumWithZero()
        {
            var result = Calculator.Sum(1, 0);
            Assert.That(result, Is.EqualTo(1));
        }
    }
    

    However, it’s not a good idea: you’ll end up with lots of identical tests (DRY, remember?) that add little to no value to the test suite. Also, this approach forces you to add a new test method to every new kind of test that pops into your mind.

    When possible, we should generalize it. With NUnit, we can use the TestCase attribute to specify the list of parameters passed in input to our test method, including the expected result.

    We can then simplify the whole test class by creating only one method that accepts the different cases in input and runs tests on those values.

    [Test]
    [TestCase(1, 5, 6)]
    [TestCase(-1, -5, -6)]
    [TestCase(1, 0, 1)]
    public void SumWorksCorrectly(int first, int second, int expected)
    {
        var result = Calculator.Sum(first, second);
        Assert.That(result, Is.EqualTo(expected));
    }
    

    By using TestCase, you can cover different cases by simply adding a new case without creating new methods.

    Clearly, don’t abuse it: use it only to group methods with similar behaviour – and don’t add if statements in the test method!

    There is a more advanced way to create a TestCase in NUnit, named TestCaseSource – but we will talk about it in a future C# tip 😉

    Further readings

    If you are using NUnit, I suggest you read this article about custom equality checks – you might find it handy in your code!

    🔗 C# Tip: Use custom Equality comparers in Nunit tests | Code4IT

    This article first appeared on Code4IT 🐧

    Wrapping up

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • 4 ways to create Unit Tests without Interfaces in C# &vert; Code4IT

    4 ways to create Unit Tests without Interfaces in C# | Code4IT


    C# devs have the bad habit of creating interfaces for every non-DTO class because «we need them for mocking!». Are you sure it’s the only way?

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    One of the most common traits of C# developers is the excessive usage of interfaces.

    For every non-DTO class we define, we usually also create the related interface. Most of the time, we don’t need it because we have multiple implementations of an interface. Instead, we say that we need an interface to enable mocking.

    That’s true; it’s pretty straightforward to mock an interface: lots of libraries, like Moq and NSubstitute, allow you to create mocks and pass them to the class under test. What if there were another way?

    In this article, we will learn how to have complete control over a dependency while having the concrete class, and not the related interface, injected in the constructor.

    C# devs always add interfaces, just in case

    If you’re a developer like me, you’ve been taught something like this:

    One of the SOLID principles is Dependency Inversion; to achieve it, you need Dependency Injection. The best way to do that is by creating an interface, injecting it in the consumer’s constructor, and then mapping the interface and the concrete class.

    Sometimes, somebody explains that we don’t need interfaces to achieve Dependency Injection. However, there are generally two arguments proposed by those who keep using interfaces everywhere: the “in case I need to change the database” argument and, even more often, the “without interfaces, I cannot create mocks”.

    Are we sure?

    The “Just in case I need to change the database” argument

    One phrase that I often hear is:

    Injecting interfaces allows me to change the concrete implementation of a class without worrying about the caller. You know, just in case I had to change the database engine…

    Yes, that’s totally right – using interfaces, you can change the internal implementation in a bat of an eye.

    Let’s be honest: in all your career, how many times have you changed the underlying database? In my whole career, it happened just once: we tried to build a solution using Gremlin for CosmosDB, but it turned out to be too expensive – so we switched to a simpler MongoDB.

    But, all in all, it wasn’t only thanks to the interfaces that we managed to switch easily; it was because we strictly separated the classes and did not leak the models related to Gremlin into the core code. We structured the code with a sort of Hexagonal Architecture, way before this term became a trend in the tech community.

    Still, interfaces can be helpful, especially when dealing with multiple implementations of the same methods or when you want to wrap your head around the methods, inputs, and outputs exposed by a module.

    The “I need to mock” argument

    Another one I like is this:

    Interfaces are necessary for mocking dependencies! Otherwise, how can I create Unit Tests?

    Well, I used to agree with this argument. I was used to mocking interfaces by using libraries like Moq and defining the behaviour of the dependency using the SetUp method.

    It’s still a valid way, but my point here is that that’s not the only one!

    One of the simplest tricks is to mark your classes as abstract. But… this means you’ll end up with every single class marked as abstract. Not the best idea.

    We have other tools in our belt!

    A realistic example: Dependency Injection without interfaces

    Let’s start with a real-ish example.

    We have a NumbersRepository that just exposes one method: GetNumbers().

    public class NumbersRepository
    {
        private readonly int[] _allNumbers;
    
        public NumbersRepository()
        {
            _allNumbers = Enumerable.Range(0, int.MaxValue).ToArray();
        }
    
        public IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    }
    

    Generally, one would be tempted to add an interface with the same name as the class, INumbersRepository, and include the GetNumbers method in the interface definition.

    We are not going to do that – the interface is not necessary, so why clutter the code with something like that?

    Now, for the consumer. We have a simple NumbersSearchService that accepts, via Dependency Injection, an instance of NumbersRepository (yes, the concrete class!) and uses it to perform a simple search:

    public class NumbersSearchService
    {
        private readonly NumbersRepository _repository;
    
        public NumbersSearchService(NumbersRepository repository)
        {
            _repository = repository;
        }
    
        public bool Contains(int number)
        {
            var numbers = _repository.GetNumbers();
            return numbers.Contains(number);
        }
    }
    

    To add these classes to your ASP.NET project, you can add them in the DI definition like this:

    builder.Services.AddSingleton<NumbersRepository>();
    builder.Services.AddSingleton<NumbersSearchService>();
    

    Without adding any interface.

    Now, how can we test this class without using the interface?

    Way 1: Use the “virtual” keyword in the dependency to create stubs

    We can create a subclass of the dependency, even if it is a concrete class, by overriding just some of its functionalities.

    For example, we can choose to mark the GetNumbers method in the NumbersRepository class as virtual, making it easily overridable from a subclass.

    public class NumbersRepository
    {
        private readonly int[] _allNumbers;
    
        public NumbersRepository()
        {
            _allNumbers = Enumerable.Range(0, 100).ToArray();
        }
    
    -    public IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    +    public virtual IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    }
    

    Yes, we can mark a method as virtual even if the class is concrete!

    Now, in our Unit Tests, we can create a subtype of NumbersRepository to have complete control of the GetNumbers method:

    internal class StubNumberRepo : NumbersRepository
    {
        private IEnumerable<int> _numbers;
    
        public void SetNumbers(params int[] numbers) => _numbers = numbers;
    
        public override IEnumerable<int> GetNumbers() => _numbers;
    }
    

    We have overridden the GetNumbers method, but to do so, we had to include a new method, SetNumbers, to define the expected result of the former method.

    We then can use it in our tests like this:

    [Test]
    public void Should_WorkWithStubRepo()
    {
        // Arrange
        var repository = new StubNumberRepo();
        repository.SetNumbers(1, 2, 3);
        var service = new NumbersSearchService(repository);
    
        // Act
        var result = service.Contains(3);
    
        // Assert
        Assert.That(result, Is.True);
    }
    

    You now have the full control over the subclass. But this approach comes with a problem: if you have multiple methods marked as virtual, and you are going to use all of them in your test classes, then you will need to override every single method (to have control over them) and work out how to decide whether to use the concrete method or the stub implementation.

    For example, we can update the StubNumberRepo to let the consumer choose if we need the dummy values or the base implementation:

    internal class StubNumberRepo : NumbersRepository
    {
        private IEnumerable<int> _numbers;
        private bool _useStubNumbers;
    
        public void SetNumbers(params int[] numbers)
        {
            _numbers = numbers;
            _useStubNumbers = true;
        }
    
        public override IEnumerable<int> GetNumbers()
        {
            if (_useStubNumbers)
                return _numbers;
            return base.GetNumbers();
        }
    }
    

    With this approach, by default, we use the concrete implementation of NumbersRepository because _useStubNumbers is false. If we call the SetNumbers method, we also specify that we don’t want to use the original implementation.

    Way 2: Use the virtual keyword in the service to avoid calling the dependency

    Similar to the previous approach, we can mark some methods of the caller as virtual to allow us to change parts of our class while keeping everything else as it was.

    To achieve it, we have to refactor a little our Service class:

    public class NumbersSearchService
    {
        private readonly NumbersRepository _repository;
    
        public NumbersSearchService(NumbersRepository repository)
        {
            _repository = repository;
        }
    
        public bool Contains(int number)
        {
    -       var numbers = _repository.GetNumbers();
    +       var numbers = GetNumbers();
            return numbers.Contains(number);
        }
    
    +    public virtual IEnumerable<int> GetNumbers() => _repository.GetNumbers();
    }
    

    The key is that we moved the calls to the external references to a separate method, marking it as virtual.

    This way, we can create a stub class of the Service itself without the need to stub its dependencies:

    internal class StubNumberSearch : NumbersSearchService
    {
        private IEnumerable<int> _numbers;
        private bool _useStubNumbers;
    
        public StubNumberSearch() : base(null)
        {
        }
    
        public void SetNumbers(params int[] numbers)
        {
            _numbers = numbers.ToArray();
            _useStubNumbers = true;
        }
    
        public override IEnumerable<int> GetNumbers()
            => _useStubNumbers ? _numbers : base.GetNumbers();
    }
    

    The approach is almost identical to the one we saw before. The difference can be seen in your tests:

    [Test]
    public void Should_UseStubService()
    {
        // Arrange
        var service = new StubNumberSearch();
        service.SetNumbers(12, 15, 30);
    
        // Act
        var result = service.Contains(15);
    
        // Assert
        Assert.That(result, Is.True);
    }
    

    There is a problem with this approach: many devs (correctly) add null checks in the constructor to ensure that the dependencies are not null:

    public NumbersSearchService(NumbersRepository repository)
    {
        ArgumentNullException.ThrowIfNull(repository);
        _repository = repository;
    }
    

    While this approach makes it safe to use the NumbersSearchService reference within the class’ methods, it also stops us from creating a StubNumberSearch. Since we want to create an instance of NumbersSearchService without the burden of injecting all the dependencies, we call the base constructor passing null as a value for the dependencies. If we validate against null, the stub class becomes unusable.

    There’s a simple solution: adding a protected empty constructor:

    public NumbersSearchService(NumbersRepository repository)
    {
        ArgumentNullException.ThrowIfNull(repository);
        _repository = repository;
    }
    
    protected NumbersSearchService()
    {
    }
    

    We mark it as protected because we want that only subclasses can access it.

    Way 3: Use the “new” keyword in methods to hide the base implementation

    Similar to the virtual keyword is the new keyword, which can be applied to methods.

    We can then remove the virtual keyword from the base class and hide its implementation by marking the overriding method as new.

    public class NumbersSearchService
    {
        private readonly NumbersRepository _repository;
    
        public NumbersSearchService(NumbersRepository repository)
        {
            ArgumentNullException.ThrowIfNull(repository);
            _repository = repository;
        }
    
        public bool Contains(int number)
        {
            var numbers = _repository.GetNumbers();
            return numbers.Contains(number);
        }
    
    -    public virtual IEnumerable<int> GetNumbers() => _repository.GetNumbers();
    +    public IEnumerable<int> GetNumbers() => _repository.GetNumbers();
    }
    

    We have restored the original implementation of the Repository.

    Now, we can update the stub by adding the new keyword.

    internal class StubNumberSearch : NumbersSearchService
    {
        private IEnumerable<int> _numbers;
        private bool _useStubNumbers;
    
        public void SetNumbers(params int[] numbers)
        {
            _numbers = numbers.ToArray();
            _useStubNumbers = true;
        }
    
    -    public override IEnumerable<int> GetNumbers() => _useStubNumbers ? _numbers : base.GetNumbers();
    +    public new IEnumerable<int> GetNumbers() => _useStubNumbers ? _numbers : base.GetNumbers();
    }
    

    We haven’t actually solved any problem except for one: we can now avoid cluttering all our classes with the virtual keyword.

    A question for you! Is there any difference between using the new and the virtual keyword? When you should pick one instead of the other? Let me know in the comments section! 📩

    Way 4: Mock concrete classes by marking a method as virtual

    Sometimes, I hear developers say that mocks are the absolute evil, and you should never use them.

    Oh, come on! Don’t be so silly!

    That’s true, when using mocks you are writing tests on a irrealistic environment. But, well, that’s exactly the point of having mocks!

    If you think about it, at school, during Science lessons, we were taught to do our scientific calculations using approximations: ignore the air resistance, ignore friction, and so on. We knew that that world did not exist, but we removed some parts to make it easier to validate our hypothesis.

    In my opinion, it’s the same for testing. Mocks are useful to have full control of a specific behaviour. Still, only relying on mocks makes your tests pretty brittle: you cannot be sure that your system is working under real conditions.

    That’s why, as I explained in a previous article, I prefer the Testing Diamond over the Testing Pyramid. In many real cases, five Integration Tests are more valuable than fifty Unit Tests.

    But still, mocks can be useful. How can we use them if we don’t have interfaces?

    Let’s start with the basic example:

    public class NumbersRepository
    {
        private readonly int[] _allNumbers;
    
        public NumbersRepository()
        {
            _allNumbers = Enumerable.Range(0, 100).ToArray();
        }
    
        public IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    }
    
    public class NumbersSearchService
    {
        private readonly NumbersRepository _repository;
    
        public NumbersSearchService(NumbersRepository repository)
        {
            ArgumentNullException.ThrowIfNull(repository);
            _repository = repository;
        }
    
        public bool Contains(int number)
        {
            var numbers = _repository.GetNumbers();
            return numbers.Contains(number);
        }
    }
    

    If we try to use Moq to create a mock of NumbersRepository (again, the concrete class) like this:

    [Test]
    public void Should_WorkWithMockRepo()
    {
        // Arrange
        var repository = new Moq.Mock<NumbersRepository>();
        repository.Setup(_ => _.GetNumbers()).Returns(new int[] { 1, 2, 3 });
        var service = new NumbersSearchService(repository.Object);
    
        // Act
        var result = service.Contains(3);
    
        // Assert
        Assert.That(result, Is.True);
    }
    

    It will fail with this error:

    System.NotSupportedException : Unsupported expression: _ => _.GetNumbers()
    Non-overridable members (here: NumbersRepository.GetNumbers) may not be used in setup / verification expressions.

    This error occurs because the implementation GetNumbers is fixed as defined in the NumbersRepository class and cannot be overridden.

    Unless you mark it as virtual, as we did before.

    public class NumbersRepository
    {
        private readonly int[] _allNumbers;
    
        public NumbersRepository()
        {
            _allNumbers = Enumerable.Range(0, 100).ToArray();
        }
    
    -    public IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    +    public virtual IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
    }
    

    Now the test passes: we have successfully mocked a concrete class!

    Further readings

    Testing is a crucial part of any software application. I personally write Unit Tests even for throwaway software – this way, I can ensure that I’m doing the correct thing without the need for manual debugging.

    However, one part that is often underestimated is the code quality of tests. Tests should be written even better than production code. You can find more about this topic here:

    🔗 Tests should be even more well-written than production code | Code4IT

    Also, Unit Tests are not enough. You should probably write more Integration Tests than Unit Tests. This one is a testing strategy called Testing Diamond.

    🔗 Testing Pyramid vs Testing Diamond (and how they affect Code Coverage) | Code4IT

    This article first appeared on Code4IT 🐧

    Clearly, you can write Integration Tests for .NET APIs easily. In this article, I explain how to create and customize Integration Tests using NUnit:

    🔗 Advanced Integration Tests for .NET 7 API with WebApplicationFactory and NUnit | Code4IT

    Wrapping up

    In this article, we learned that it’s not necessary to create interfaces for the sake of having mocks.

    We have different other options.

    Honestly speaking, I’m still used to creating interfaces and using them with mocks.

    I find it easy to do, and this approach provides a quick way to create tests and drive the behaviour of the dependencies.

    Also, I recognize that interfaces created for the sole purpose of mocking are quite pointless: we have learned that there are other ways, and we should consider trying out these solutions.

    Still, interfaces are quite handy for two “non-technical” reasons:

    • using interfaces, you can understand in a glimpse what are the operations that you can call in a clean and concise way;
    • interfaces and mocks allow you to easily use TDD: while writing the test cases, you also define what methods you need and the expected behaviour. I know you can do that using stubs, but I find it easier with interfaces.

    I know, this is a controversial topic – I’m not saying that you should remove all your interfaces (I think it’s a matter of personal taste, somehow!), but with this article, I want to highlight that you can avoid interfaces.

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • Updating to .NET 8, updating to IHostBuilder, and running Playwright Tests within NUnit headless or headed on any OS

    Updating to .NET 8, updating to IHostBuilder, and running Playwright Tests within NUnit headless or headed on any OS



    All the Unit Tests passI’ve been doing not just Unit Testing for my sites but full on Integration Testing and Browser Automation Testing as early as 2007 with Selenium. Lately, however, I’ve been using the faster and generally more compatible Playwright. It has one API and can test on Windows, Linux, Mac, locally, in a container (headless), in my CI/CD pipeline, on Azure DevOps, or in GitHub Actions.

    For me, it’s that last moment of truth to make sure that the site runs completely from end to end.

    I can write those Playwright tests in something like TypeScript, and I could launch them with node, but I like running end unit tests and using that test runner and test harness as my jumping off point for my .NET applications. I’m used to right clicking and “run unit tests” or even better, right click and “debug unit tests” in Visual Studio or VS Code. This gets me the benefit of all of the assertions of a full unit testing framework, and all the benefits of using something like Playwright to automate my browser.

    In 2018 I was using WebApplicationFactory and some tricky hacks to basically spin up ASP.NET within .NET (at the time) Core 2.1 within the unit tests and then launching Selenium. This was kind of janky and would require to manually start a separate process and manage its life cycle. However, I kept on with this hack for a number of years basically trying to get the Kestrel Web Server to spin up inside of my unit tests.

    I’ve recently upgraded my main site and podcast site to .NET 8. Keep in mind that I’ve been moving my websites forward from early early versions of .NET to the most recent versions. The blog is happily running on Linux in a container on .NET 8, but its original code started in 2002 on .NET 1.1.

    Now that I’m on .NET 8, I scandalously discovered (as my unit tests stopped working) that the rest of the world had moved from IWebHostBuilder to IHostBuilder five version of .NET ago. Gulp. Say what you will, but the backward compatibility is impressive.

    As such my code for Program.cs changed from this

    public static void Main(string[] args)
    {
    CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>();

    to this:

    public static void Main(string[] args)
    {
    CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args).
    ConfigureWebHostDefaults(WebHostBuilder => WebHostBuilder.UseStartup<Startup>());

    Not a major change on the outside but tidies things up on the inside and sets me up with a more flexible generic host for my web app.

    My unit tests stopped working because my Kestral Web Server hack was no longer firing up my server.

    Here is an example of my goal from a Playwright perspective within a .NET NUnit test.

    [Test]
    public async Task DoesSearchWork()
    {
    await Page.GotoAsync(Url);

    await Page.Locator("#topbar").GetByRole(AriaRole.Link, new() { Name = "episodes" }).ClickAsync();

    await Page.GetByPlaceholder("search and filter").ClickAsync();

    await Page.GetByPlaceholder("search and filter").TypeAsync("wife");

    const string visibleCards = ".showCard:visible";

    var waiting = await Page.WaitForSelectorAsync(visibleCards, new PageWaitForSelectorOptions() { Timeout = 500 });

    await Expect(Page.Locator(visibleCards).First).ToBeVisibleAsync();

    await Expect(Page.Locator(visibleCards)).ToHaveCountAsync(5);
    }

    I love this. Nice and clean. Certainly here we are assuming that we have a URL in that first line, which will be localhost something, and then we assume that our web application has started up on its own.

    Here is the setup code that starts my new “web application test builder factory,” yeah, the name is stupid but it’s descriptive. Note the OneTimeSetUp and the OneTimeTearDown. This starts my web app within the context of my TestHost. Note the :0 makes the app find a port which I then, sadly, have to dig out and put into the Url private for use within my Unit Tests. Note that the <Startup> is in fact my Startup class within Startup.cs which hosts my app’s pipeline and Configure and ConfigureServices get setup here so routing all works.

    private string Url;
    private WebApplication? _app = null;

    [OneTimeSetUp]
    public void Setup()
    {
    var builder = WebApplicationTestBuilderFactory.CreateBuilder<Startup>();

    var startup = new Startup(builder.Environment);
    builder.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Loopback, 0));
    startup.ConfigureServices(builder.Services);
    _app = builder.Build();

    // listen on any local port (hence the 0)
    startup.Configure(_app, _app.Configuration);
    _app.Start();

    //you are kidding me
    Url = _app.Services.GetRequiredService<IServer>().Features.GetRequiredFeature<IServerAddressesFeature>().Addresses.Last();
    }

    [OneTimeTearDown]
    public async Task TearDown()
    {
    await _app.DisposeAsync();
    }

    So what horrors are buried in WebApplicationTestBuilderFactory? The first bit is bad and we should fix it for .NET 9. The rest is actually every nice, with a hat tip to David Fowler for his help and guidance! This is the magic and the ick in one small helper class.

    public class WebApplicationTestBuilderFactory 
    {
    public static WebApplicationBuilder CreateBuilder<T>() where T : class
    {
    //This ungodly code requires an unused reference to the MvcTesting package that hooks up
    // MSBuild to create the manifest file that is read here.
    var testLocation = Path.Combine(AppContext.BaseDirectory, "MvcTestingAppManifest.json");
    var json = JsonObject.Parse(File.ReadAllText(testLocation));
    var asmFullName = typeof(T).Assembly.FullName ?? throw new InvalidOperationException("Assembly Full Name is null");
    var contentRootPath = json?[asmFullName]?.GetValue<string>();

    //spin up a real live web application inside TestHost.exe
    var builder = WebApplication.CreateBuilder(
    new WebApplicationOptions()
    {
    ContentRootPath = contentRootPath,
    ApplicationName = asmFullName
    });
    return builder;
    }
    }

    The first 4 lines are nasty. Because the test runs in the context of a different directory and my website needs to run within the context of its own content root path, I have to force the content root path to be correct and the only way to do that is by getting the apps base directory from a file generated within MSBuild from the (aging) MvcTesting package. The package is not used, but by referencing it it gets into the build and makes that file that I then use to pull out the directory.

    If we can get rid of that “hack” and pull the directory from context elsewhere, then this helper function turns into a single line and .NET 9 gets WAY WAY more testable!

    Now I can run my Unit Tests AND Playwright Browser Integration Tests across all OS’s, headed or headless, in docker or on the metal. The site is updated to .NET 8 and all is right with my code. Well, it runs at least. 😉




    About Scott

    Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

    facebook
    bluesky
    subscribe
    About   Newsletter

    Hosting By
    Hosted on Linux using .NET in an Azure App Service










    Source link