Testing EventGridTrigger Azure Functions Locally (Without Using ngrok)

(This post refers to Azure Functions v2)

One way to test Azure Functions that use Event Grid triggers is to run the Function App locally and then get Azure in the cloud to invoke the function running on the local machine. As an example, suppose you want to use Event Grid to improve the reliability and responsiveness of Blob Storage processing. To do this the documentation suggests the use of ngrok. Now when a blob is added to a container in the cloud, the locally running function on the dev machine will be invoked via ngrok.

There is a somewhat simpler solution that allows you to invoke the Event Grid triggered function locally.

This approach bypasses Event Grid completely, so it is not a substitute for proper end-to-end testing, it’s more a development-time testing & debugging tool.

Manually Running Non HTTP-Triggered Azure Functions

You can manually trigger a non HTTP-triggered function (such as a timer triggered or Event Grid triggered function) via a special HTTP endpoint.

The endpoint is of the format: {host}/admin/functions/{function name}

For example, take the following function (which was also used in the post Improving Azure Functions Blob Trigger Performance and Reliability - Part 3: Using Event Grid to Respond to New Blobs):

public static class ProcessFoodBlobsEventGrid
{
    private static readonly string[] _meats = { "steak", "chicken", "venison" };

    [FunctionName("ProcessFoodBlobsEventGrid")]
    public static void Run(
     [EventGridTrigger]EventGridEvent blobCreatedEvent,
     [Blob("{data.url}")] string foods, // assumes small blob size so using string not stream
     [Blob("{data.url}.vegetarian")] out string vegetarian,
     [Blob("{data.url}.nonvegetarian")] out string nonVegetarian,
     ILogger log)
    {
        log.LogInformation("Processing a blob created event");

        StorageBlobCreatedEventData createdEvent = ((JObject)blobCreatedEvent.Data).ToObject<StorageBlobCreatedEventData>();

        log.LogInformation($"Blob: {createdEvent.Url}");
        log.LogInformation($"Api operation: {createdEvent.Api}");

        vegetarian = null;
        nonVegetarian = null;

        string[] foodLines = foods.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);


        foreach (var food in foodLines)
        {
            var isMeat = _meats.Contains(food);

            if (isMeat)
            {
                nonVegetarian += food + Environment.NewLine;
            }
            else
            {
                vegetarian += food + Environment.NewLine;
            }
        }
    }
}

The preceding function when running locally in development would have the special URL: http://localhost:7071/admin/functions/ProcessFoodBlobsEventGrid

If you had a timer-triggered function called HerdCats that you wanted to manually invoke (so you didn’t have to wait for the next timed invocation) the special URL would be:http://localhost:7071/admin/functions/HerdCats

Note: when running locally in development you do not have to authenticate. If you wanted to manually invoke a deployed function in Azure, you need to provide an x-functions-key header that contains the function master key.

Manually Invoking an Event Grid Triggered Azure Function

When using the special URL to invoke a function, you can also provide data to be passed to the function. The type of data passed will depend on the trigger type of the function that you are invoking.

To provide data to the function, a JSON payload can be posted to the special URL. The data that is passed to the function is contained in a JSON property called “input”:

{
    "input": "trigger data goes here"
}

If the Event Grid triggered function will be invoked by a new blob event, the contents of this input property must match the event schema for an Azure Blob Storage event.

An example of event JSON (taken from the Microsoft documentation):

[{
  "topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount",
  "subject": "/blobServices/default/containers/testcontainer/blobs/testfile.txt",
  "eventType": "Microsoft.Storage.BlobCreated",
  "eventTime": "2017-06-26T18:41:00.9584103Z",
  "id": "831e1650-001e-001b-66ab-eeb76e069631",
  "data": {
    "api": "PutBlockList",
    "clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760",
    "requestId": "831e1650-001e-001b-66ab-eeb76e000000",
    "eTag": "0x8D4BCC2E4835CD0",
    "contentType": "text/plain",
    "contentLength": 524288,
    "blobType": "BlockBlob",
    "url": "https://example.blob.core.windows.net/testcontainer/testfile.txt",
    "sequencer": "00000000000004420000000000028963",
    "storageDiagnostics": {
      "batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0"
    }
  },
  "dataVersion": "",
  "metadataVersion": "1"
}]

When testing the function outlined earlier, the first thing to do is ensure that there is a blob in the local blob container that will be read by the function by way of the blob input binding: [Blob("{data.url}")] string foods.

For example, in the Storage Emulator a blob called in.txt can be uploaded to the food-in container.

Now the new blob event data JSON needs to be modified, specifically the data.url property needs to contain the URL to the local blob: http://127.0.0.1:10000/devstoreaccount1/food-in/in.txt

A modified version with updated data.url would be as follows:

[{
  "topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount",
  "subject": "/blobServices/default/containers/testcontainer/blobs/testfile.txt",
  "eventType": "Microsoft.Storage.BlobCreated",
  "eventTime": "2017-06-26T18:41:00.9584103Z",
  "id": "831e1650-001e-001b-66ab-eeb76e069631",
  "data": {
    "api": "PutBlockList",
    "clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760",
    "requestId": "831e1650-001e-001b-66ab-eeb76e000000",
    "eTag": "0x8D4BCC2E4835CD0",
    "contentType": "text/plain",
    "contentLength": 524288,
    "blobType": "BlockBlob",
    "url": "http://127.0.0.1:10000/devstoreaccount1/food-in/in.txt",
    "sequencer": "00000000000004420000000000028963",
    "storageDiagnostics": {
      "batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0"
    }
  },
  "dataVersion": "",
  "metadataVersion": "1"
}]

The next step is to remove the surrounding [], and replace the with . Then paste the resulting JSON into the input property:

{
    "input": "
  {
    'topic': '/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount',
    'subject': '/blobServices/default/containers/oc2d2817345i200097container/blobs/oc2d2817345i20002296blob',
    'eventType': 'Microsoft.Storage.BlobCreated',
    'eventTime': '2017-06-26T18:41:00.9584103Z',
    'id': '831e1650-001e-001b-66ab-eeb76e069631',
    'data': {
      'api': 'PutBlockList',
      'clientRequestId': '6d79dbfb-0e37-4fc4-981f-442c9ca65760',
      'requestId': '831e1650-001e-001b-66ab-eeb76e000000',
      'eTag': '0x8D4BCC2E4835CD0',
      'contentType': 'application/octet-stream',
      'contentLength': 524288,
      'blobType': 'BlockBlob',
      'url': 'http://127.0.0.1:10000/devstoreaccount1/food-in/in.txt',
      'sequencer': '00000000000004420000000000028963',
      'storageDiagnostics': {
        'batchId': 'b68529f3-68cd-4744-baa4-3c0498ec19f0'
      }
    },
    'dataVersion': '',
    'metadataVersion': '1'
  }
"
}

Now this JSON can be POSTed to the special URL: in the case of the example in this post the URL would be: http://localhost:7071/admin/functions/ProcessFoodBlobsEventGrid

The following screenshot shows posting using Postman:

 

Using Postman to post to Event Grid triggered Azure Function

Posting will cause the Event Grid triggered function to be invoked and the JSON contained inside the input property will be passed to the trigger input EventGridEvent blobCreatedEvent object. The function will execute and read in the blob called “in.txt”.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

NUnit 3 Quick Tips: Asserting On Collections

When the result you want to check is a collection, you can use NUnit to assert that it has the expected number of items or is empty, that all items are unique, that specific items do/not exist, and that items exist that satisfy some condition or predicate.

Asserting on the Number of Items in a Collection with NUnit Asserts

var names = new[] { "Sarah", "Amrit", "Amanda", "Sarah" };

Assert.That(names, Has.Exactly(4).Items); // pass
Assert.That(names, Is.Empty); // fail
Assert.That(names, Is.Not.Empty); // pass

Asserting That All Items in a Collection are Unique with NUnit Asserts

Assert.That(names, Is.Unique); // fail - 2 Sarah items exist

Asserting That An Item Does or Does Not Exist in a Collection with NUnit Asserts

Assert.That(names, Contains.Item("Sarah")); // pass

// Alternative syntax
Assert.That(names, Does.Contain("Sarah")); // pass
Assert.That(names, Does.Not.Contain("Arnold")); // pass

Asserting That An Item Appears a Specified Number Of Times in a Collection with NUnit Asserts

Assert.That(names, Has.Exactly(1).EqualTo("Sarah")); // fail
Assert.That(names, Has.Exactly(2).EqualTo("Sarah")); // pass
Assert.That(names, Has.Exactly(2).EqualTo("Sarah")
                      .And.Exactly(1).EqualTo("Amrit")); // pass

Asserting That All Items In a Collections Satisfy a Predicate/Condition with NUnit Asserts

Assert.That(names, Is.All.Not.Null); // pass
Assert.That(names, Is.All.Contains("a")); // fail lowercase a
Assert.That(names, Is.All.Contains("a").IgnoreCase); // pass
Assert.That(names, Is.All.Matches<string>(name => name.ToUpperInvariant().Contains("A"))); // pass
Assert.That(names, Is.All.Matches<string>(name => name.Length > 4)); // pass

Asserting That Only One Item In a Collection Satisfies a Predicate with NUnit Asserts

Assert.That(names, Has.Exactly(1).Matches<string>(name => name.Contains("mri"))); // pass
Assert.That(names, Has.Exactly(1).Matches<string>(name => name.Contains("ara"))); // fail (2 Sarah items exist)

To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

You can start watching with a Pluralsight free trial.

SHARE:

NUnit 3 Quick Tips: Asserting On Object Reference Equality

When asserting on equality using the EqualConstraint you may not always get the behaviour you want depending on what objects are being asserted on. This can be influenced by whether or not the objects are value or reference types and if the type implements or overrides methods such as IEquatable<T> or object.Equals overrides.

Asserting on Value Type Equality with NUnit

int a = 42;
int b = 42;

Assert.That(a, Is.EqualTo(b)); // pass - values are same, ints are structs with value semantics
Assert.That(a, Is.SameAs(b)); // fail - a and b do not point to the same object in memory

int c = a;

Assert.That(c, Is.EqualTo(a)); // pass - values are same

Asserting on Reference Type Equality with NUnit

By default, 2 instances of a reference type will not pass an equality assert:

class Person
{
    public string Name { get; set; }
}
Person p1 = new Person { Name = "Sarah" };
Person p2 = new Person { Name = "Sarah" };

Assert.That(p1, Is.EqualTo(p2)); // fail, Person is class with reference semantics

Asserting That Two References Point to the Same Object with NUnit

If you want to assert that 2 object references point to the same object you can use the SameAsConstraint:

Assert.That(p1, Is.SameAs(p2)); // fail, p1 and p2 point to different objects in memory Person p3 = p1; Assert.That(p3, Is.SameAs(p1)); // pass, p3 and p1 point to same object in memory Assert.That(p3, Is.Not.SameAs(p2)); // pass, p3 and p2 point to different objects in memory

Customizing Equality Asserts with NUnit

There are a number of ways to influence how NUnit performs equality assertions including implementing IEquatable<T>:

class Employee : IEquatable<Employee>
{
    public string Name { get; set; }

    public bool Equals(Employee other)
    {
        if (other is null)
        {
            return false;
        }

        return Name == other.Name;
    }
}
Employee e1 = new Employee { Name = "Sarah" };
Employee e2 = new Employee { Name = "Sarah" };

Assert.That(e1, Is.EqualTo(e2)); // pass - IEquatable<Employee>.Equals implementation is used

To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

You can start watching with a Pluralsight free trial.

SHARE:

NUnit 3 Quick Tips: Asserting Within Ranges

If you are asserting that a value is equal to something and you want to specify some tolerance you can do so.

Specifying a Range for Values with NUnit Asserts (e.g. int)

var i = 42;

Assert.That(i, Is.EqualTo(40)); // fail

Assert.That(i, Is.EqualTo(40).Within(2)); // pass

Assert.That(i, Is.EqualTo(40).Within(1)); // fail "Expected: 40 +/- 1"

Specifying a Range as a Percentage with NUnit Asserts

In addition to specifying a range tolerance as a fixed value you can also specify it as a percentage:

Assert.That(i, Is.EqualTo(40).Within(5).Percent); // pass

Assert.That(i, Is.EqualTo(40).Within(4).Percent); // fail "Expected: 40 +/- 4 Percent"

Specifying a Range for DateTime Objects with NUnit Asserts

When working with DateTimes you can specify the tolerance as a TimeSpan instance:

var newYearsDay2019 = new DateTime(2019, 1, 1);

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(TimeSpan.FromDays(1))); // pass

Or instead of using a TimeSpan you can use one of the convenience modifiers:

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(1).Days); // pass

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(24).Hours); // pass
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(23).Hours); // fail

var numberOfMinutesInADay = 24 * 60;
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(numberOfMinutesInADay).Minutes); // pass
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(numberOfMinutesInADay - 1).Minutes); // fail "Expected: 2019-01-02 00:00:00 +/- 23:59:00"

// Also Within(n).Seconds .Milliseconds and .Ticks

To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

You can start watching with a Pluralsight free trial.

SHARE:

Mocking HttpRequest Body Content When Testing Azure Function HTTP Trigger Functions

When creating Azure Functions that are triggered by an HTTP request, you may want to write unit tests for the function Run method. These unit tests can be executed outside of the Azure Functions runtime, just like testing regular methods.

If your HTTP-triggered function takes as the function input an HttpRequest (as opposed to an automatically JSON-deserialized class) you may need to provide request data in your test.

As an example, consider the following code snippet that defines an HTTP-triggered function.

[FunctionName("Portfolio")]
[return: Queue("deposit-requests")]
public static async Task<DepositRequest> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = "portfolio/{investorId}")]HttpRequest req,
    [Table("Portfolio", InvestorType.Individual, "{investorId}")] Investor investor,
    string investorId,
    ILogger log)
{
    log.LogInformation($"C# HTTP trigger function processed a request.");

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

    log.LogInformation($"Request body: {requestBody}");

    var deposit = JsonConvert.DeserializeObject<Deposit>(requestBody);

    // etc.
}

If the  the preceding code is executed in a test, some content needs to be provided to be used when accessing req.Body. To do this using Moq a mock HttpRequest can be created that returns a specified Stream instance for req.Body.

If you want to create a request body that contains a JSON payload, you can use the following helper method in your tests:

private static Mock<HttpRequest> CreateMockRequest(object body)
{            
    var ms = new MemoryStream();
    var sw = new StreamWriter(ms);

    var json = JsonConvert.SerializeObject(body);

    sw.Write(json);
    sw.Flush();

    ms.Position = 0;

    var mockRequest = new Mock<HttpRequest>();
    mockRequest.Setup(x => x.Body).Returns(ms);

    return mockRequest;
}

As an example of using this method in a test:

[Fact]
public async Task ReturnCorrectDepositInformation()
{
    var deposit = new Deposit { Amount = 42 };
    var investor = new Investor { };

    Mock<HttpRequest> mockRequest = CreateMockRequest(deposit);

    DepositRequest result = await Portfolio.Run(mockRequest.Object, investor, "42", new Mock<ILogger>().Object);

    Assert.Equal(42, result.Amount);
    Assert.Same(investor, result.Investor);
}

When the preceding test is run, the function run method will get the contents of the memory stream that contains the JSON.

To learn more about using Moq to create/configure/use mock objects check out my Mocking in .NET Core Unit Tests with Moq: Getting Started Pluralsight course. or to learn more about MemoryStream and how to work with streams in C# check out my Working with Files and Streams course.

You can start watching with a Pluralsight free trial.

SHARE:

Setting Up Mock ref Return Values in Moq

I recently received a message related to my Mocking in .NET Core Unit Tests with Moq: Getting Started Pluralsight course asking how to set the values of ref parameters.

As a (somewhat contrived) example, consider the following code:

public interface IParser
{
    bool TryParse(string value, ref int output);
}

public class Thing
{
    private readonly IParser _parser;

    public Thing(IParser parser)
    {
        _parser = parser;
    }

    public string ConvertStringIntToHex(string number)
    {
        int i = 0;

        if (_parser.TryParse(number, ref i))
        {
            return i.ToString("X");
        }

        throw new ArgumentException("The value supplied cannot be parsed into an int.", nameof(number));
    }
}

The Thing class requires an IParser to be able to work. In a test, a mocked version of an IParser can be created by Moq as the following initial test demonstrates:

[Fact]
public void ReturnHex_Fail_NoSetup()
{
    var mockParser = new Mock<IParser>();

    var sut = new Thing(mockParser.Object);

    var result = sut.ConvertStringIntToHex("255"); // fails with ArgumentException

    Assert.Equal("FF", result);
}

The preceding test will fail however because the mocked TryParse has not been configured correctly, for example specifying that the method should return true.

The following modified test attempts to fix this:

[Fact]
public void ReturnHex_Fail_NoRefValueSetup()
{
    var mockParser = new Mock<IParser>();
    mockParser.Setup(x => x.TryParse(It.IsAny<string>(), ref It.Ref<int>.IsAny))
              .Returns(true);

    var sut = new Thing(mockParser.Object);

    var result = sut.ConvertStringIntToHex("255");

    Assert.Equal("FF", result); // Fails, actual result == 0
}

In the preceding code, the return value is being set, but nowhere is the ref int output “return value” being configured.

In the following test the Callback method is used to set the ref value. To be able to do this, a delegate must first be defined that matches the signature of the mocked method that contains the ref parameter. Once this delegate is defined it can be used in the Callback method as the following code demonstrates:

// Define a delegate that can be used to set the ref value in the mocked TryParse method 
delegate void MockTryParseCallback(string number, ref int output);

[Fact]
public void ReturnHex()
{
    var mockParser = new Mock<IParser>();
    mockParser.Setup(x => x.TryParse("255", ref It.Ref<int>.IsAny)) // When the TryParse method is called with 255
              .Callback(new MockTryParseCallback((string s, ref int output) => output = 255)) // Execute callback delegate and set the ref value
              .Returns(true); // Return true as the result of the TryParse method

    var sut = new Thing(mockParser.Object);

    var result = sut.ConvertStringIntToHex("255");

    Assert.Equal("FF", result);
}

If you’ve never used Moq or want to learn more about it check out the official Moq quickstart  or head over to my Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Unit Testing C# File Access Code with System.IO.Abstractions

It can be difficult  to write unit tests for code that accesses the file system.

It’s possible to write integration tests that read in an actual file from the file system, do some processing, and check the resultant output file (or result) for correctness. There are a number of potential problems with these types of integration tests including the potential for them to more run slowly (real IO access overheads), additional test file management/setup code, etc. (this does not mean that some integration tests wouldn’t be useful however).

The System.IO.Abstractions NuGet package can help to make file access code more testable. This package provides a layer of abstraction over the file system that is API-compatible with existing code.

Take the following code as an example:

using System.IO;
namespace ConsoleApp1
{
    public class FileProcessorNotTestable
    {
        public void ConvertFirstLineToUpper(string inputFilePath)
        {
            string outputFilePath = Path.ChangeExtension(inputFilePath, ".out.txt");

            using (StreamReader inputReader = File.OpenText(inputFilePath))
            using (StreamWriter outputWriter = File.CreateText(outputFilePath))
            {
                bool isFirstLine = true;

                while (!inputReader.EndOfStream)
                {
                    string line = inputReader.ReadLine();

                    if (isFirstLine)
                    {
                        line = line.ToUpperInvariant();
                        isFirstLine = false;
                    }

                    outputWriter.WriteLine(line);
                }
            }
        }
    }
}

The preceding code opens a text file, and writes it to a new output file, but with the first line converted to uppercase.

This class is not easy to unit test however, it is tightly coupled to the physical file system with the calls to File.OpenText and File.CreateText.

Once the System.IO.Abstractions NuGet package is installed, the class can be refactored as follows:

using System.IO;
using System.IO.Abstractions;

namespace ConsoleApp1
{
    public class FileProcessorTestable
    {
        private readonly IFileSystem _fileSystem;

        public FileProcessorTestable() : this (new FileSystem()) {}

        public FileProcessorTestable(IFileSystem fileSystem)
        {
            _fileSystem = fileSystem;
        }

        public void ConvertFirstLineToUpper(string inputFilePath)
        {
            string outputFilePath = Path.ChangeExtension(inputFilePath, ".out.txt");

            using (StreamReader inputReader = _fileSystem.File.OpenText(inputFilePath))
            using (StreamWriter outputWriter = _fileSystem.File.CreateText(outputFilePath))
            {
                bool isFirstLine = true;

                while (!inputReader.EndOfStream)
                {
                    string line = inputReader.ReadLine();

                    if (isFirstLine)
                    {
                        line = line.ToUpperInvariant();
                        isFirstLine = false;
                    }

                    outputWriter.WriteLine(line);
                }
            }
        }
    }
}

The key things to notice in the preceding code is the ability to pass in an IFileSystem as a constructor parameter. The calls to File.OpenText and File.CreateText are now redirected to _fileSystem.File.OpenText and _fileSystem.File.CreateText  respectively.

If the parameterless constructor is used (e.g. in production at runtime) an instance of FileSystem will be used, however at test time, a mock IFileSystem can be supplied.

Handily, the System.IO.Abstractions.TestingHelpers NuGet package provides a pre-built mock file system that can be used in unit tests, as the following simple test demonstrates:

using System.IO.Abstractions.TestingHelpers;
using Xunit;

namespace XUnitTestProject1
{
    public class FileProcessorTestableShould
    {
        [Fact]
        public void ConvertFirstLine()
        {
            var mockFileSystem = new MockFileSystem();

            var mockInputFile = new MockFileData("line1\nline2\nline3");

            mockFileSystem.AddFile(@"C:\temp\in.txt", mockInputFile);

            var sut = new FileProcessorTestable(mockFileSystem);
            sut.ConvertFirstLineToUpper(@"C:\temp\in.txt");

            MockFileData mockOutputFile = mockFileSystem.GetFile(@"C:\temp\in.out.txt");

            string[] outputLines = mockOutputFile.TextContents.SplitLines();

            Assert.Equal("LINE1", outputLines[0]);
            Assert.Equal("line2", outputLines[1]);
            Assert.Equal("line3", outputLines[2]);
        }
    }
}

To see this in action or to learn more about file access, check out my Working with Files and Streams in C# Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Testing for Thrown Exceptions in MSTest V2

In previous posts we looked at testing for thrown exceptions in xUnit.net and NUnit. In this post we’re going to see how to test in MSTest V2.

As with the previous posts, the class being tested is as follows:

public class TemperatureSensor
{
    bool _isInitialized;

    public void Initialize()
    {
        // Initialize hardware interface
        _isInitialized = true;
    }

    public int ReadCurrentTemperature()
    {
        if (!_isInitialized)
        {
            throw new InvalidOperationException("Cannot read temperature before initializing.");
        }

        // Read hardware temp
        return 42; // Simulate for demo code purposes
    }
}

And the first test to check the normal execution:

[TestMethod]
public void ReadTemperature()
{
    var sut = new TemperatureSensor();

    sut.Initialize();

    var temperature = sut.ReadCurrentTemperature();

    Assert.AreEqual(42, temperature);
}

Next, a test can be written to check that the expected exception is thrown:

[TestMethod]
public void ErrorIfReadingBeforeInitialized()
{
    var sut = new TemperatureSensor();

    Assert.ThrowsException<InvalidOperationException>(() => sut.ReadCurrentTemperature());
}

The preceding code using the Assert.ThrowsException method, this method takes the type of the expected exception as the generic type parameter (in this case InvalidOperationException). As the method parameter an action/function can be specified – this is the code that is supposed to cause the exception to be thrown.

The thrown exception can also be captured if you need to test the exception property values:

[TestMethod]
public void ErrorIfReadingBeforeInitialized_CaptureExDemo()
{
    var sut = new TemperatureSensor();

    var ex = Assert.ThrowsException<InvalidOperationException>(() => sut.ReadCurrentTemperature());

    Assert.AreEqual("Cannot read temperature before initializing.", ex.Message);
}

To learn more about using exceptions to handle errors in C#, check out my Error Handling in C# with Exceptions Pluralsight course or to learn more about MS Test V2 check out my Automated Testing with MSTest V2 Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Testing for Thrown Exceptions in NUnit

In a previous post, testing for thrown exceptions using xUnit.net was demonstrated. In this post we’ll see how to do the same with NUnit.

Once again the class being tested is as follows:

public class TemperatureSensor
{
    bool _isInitialized;

    public void Initialize()
    {
        // Initialize hardware interface
        _isInitialized = true;
    }

    public int ReadCurrentTemperature()
    {
        if (!_isInitialized)
        {
            throw new InvalidOperationException("Cannot read temperature before initializing.");
        }

        // Read hardware temp
        return 42; // Simulate for demo code purposes
    }
}

The first test can be to test the happy path:

[Test]
public void ReadTemperature()
{
    var sut = new TemperatureSensor();

    sut.Initialize();

    var temperature = sut.ReadCurrentTemperature();

    Assert.AreEqual(42, temperature);
}

Next, a test can be written to check that the expected exception is thrown:

[Test]
public void ErrorIfReadingBeforeInitialized()
{
    var sut = new TemperatureSensor();

    Assert.Throws<InvalidOperationException>(() => sut.ReadCurrentTemperature());
}

Notice in the preceding code that any InvalidOperationException thrown will pass the test. To ensure that the thrown exception is correct, it can be captured and further asserts performed against it:

[Test]
public void ErrorIfReadingBeforeInitialized_CaptureExDemo()
{
    var sut = new TemperatureSensor();

    var ex = Assert.Throws<InvalidOperationException>(() => sut.ReadCurrentTemperature());

    Assert.AreEqual("Cannot read temperature before initializing.", ex.Message);
    // or:
    Assert.That(ex.Message, Is.EqualTo("Cannot read temperature before initializing."));
}

There’s also other ways to assert against expected exceptions such as the following:

Assert.Throws(Is.TypeOf<InvalidOperationException>()
                .And.Message.EqualTo("Cannot read temperature before initializing."), 
              () => sut.ReadCurrentTemperature());

There’s some personal preference involved when choosing a style, for example the preceding code could be considered more verbose by some and may muddle the distinction between the Act and Assert phases of a test.

To learn more about using exceptions to handle errors in C#, check out my Error Handling in C# with Exceptions Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Lifelike Test Data Generation with Bogus

Bogus is a lovely library from Brian Chavez to use in automated tests to automatically generate test data of different kinds.

As an example suppose the following class is involved in a unit test:

public class Review
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public int Rating { get; set; }
    public DateTimeOffset Created { get; set; }

    public override string ToString()
    {
        return $"{Id} '{Title}'";
    }
}

In a test, a Review instance may need properties populating with values. This could be done manually, for example to check the ToString() implementation:

[Fact]
public void BeRepresentedAsAString()
{
    var sut = new Review
    {
        Id = 42,
        Title = "blah blah"
    };

    Assert.Equal("42 'blah blah'", sut.ToString());
}

Notice in the preceding test, the actual values and title don’t really matter, only the fact that they’re joined as part of the ToString() call. In this example the values for Id and Title could be considered anonymous variable / values in that we don’t really care about them.

The following test uses the Bogus NuGet package and uses its non-fluent facade syntax:

[Fact]
public void BeRepresentedAsAString_BogusFacadeSyntax()
{
    var faker = new Faker("en"); // default en

    var sut = new Review
    {
        Id = faker.Random.Number(),
        Title = faker.Random.String()
    };

    Assert.Equal($"{sut.Id} '{sut.Title}'", sut.ToString());
}

Bogus also has a powerful fluent syntax to define what a test object will look like. To use the fluent version, a Faker<T> instance is created where T is the test object to be configured and created, for example:

[Fact]
public void BeRepresentedAsAString_BogusFluentSyntax()
{
    var reviewFaker = new Faker<Review>()
        .RuleFor(x => x.Id, f => f.Random.Number(1, 10))
        .RuleFor(x => x.Title, f => f.Lorem.Sentence());

    var sut = reviewFaker.Generate(); 

    Assert.Equal($"{sut.Id} '{sut.Title}'", sut.ToString());
}

The first argument to the RuleFor() methods allows the property of the Review object to be selected and the second argument specifies how the property value should be generated. There is a huge range of test data types supported. In the preceding code the Random API is used as well as the Lorem API.

Some examples of the types of auto generated data include:

  • Addresses: ZipCode, City, Country, Latitude, etc.
  • Commerce: Department name, ProductName, ProductAdjective, Price, etc.
  • Company: CompanyName, CatchPhrase, Bs, etc.
  • Date: Past, Soon, Between, etc.
  • Finance: Account number, TransactionType, Currency, CreditCardNumber, etc.
  • Image URL: Random image, Animals image, Nature image, etc.
  • Internet: Email, DomainName, Ipv6, Password, etc.
  • Lorem: single word, Words, Sentence, Paragraphs, etc.
  • Name: FirstName, LastName, etc.
  • Rant: Random user review, etc.
  • System: FileName, MimeType, FileExt, etc.

Some of the random generated values are quite entertaining, for example Rant.Review() may produce "My co-worker Fate has one of these. He says it looks tall."; Company.Bs() may produce "transition cross-media users", and Company.CatchPhrase() may produce "Face to face object-oriented focus group".

Bogus configuration is quite powerful and allows fairly complex setup as the following code demonstrates:

[Fact]
public void CalculateAverageRatingWhenMultipleReviews()
{
    int rating = 0;

    var reviewFaker = new Faker<Review>()
        .RuleFor(x => x.Id, f => f.Random.Number(1, 10))
        .RuleFor(x => x.Rating, f => rating++);

    var productFaker = new Faker<Product>()
        .RuleFor(x => x.PricePerUnit, f => f.Finance.Amount())
        .RuleFor(x => x.Description, f => f.WaffleText(3))
        .FinishWith((f, x) =>
            {
                reviewFaker.Generate(3).ForEach(r => x.Reviews.Add(r));
            });

    var sut = productFaker.Generate();

    Assert.Equal(1, sut.AverageRating); // (0 + 1 + 2) / 3
}

The WaffleText() API is provided by one of the extensions to Bogus (WaffleGenerator.Bogus) that produces inane looking waffle text such as the following:

The Quality Of Hypothetical Aesthetic

"The parallel personal hardware cannot explain all the problems in maximizing the efficacy of any fundamental dichotomies of the logical psychic principle. Generally the requirements of unequivocal reciprocal individuality is strictly significant. On the other hand the characteristic organizational change reinforces the weaknesses in the evolution of metaphysical terminology over a given time limit. The objective of the explicit heuristic discordance is to delineate the truly global on-going flexibility or the preliminary qualification limit. A priority should be established based on a combination of functional baseline and inevitability of amelioration The Quality Of Hypothetical Aesthetic"

 - Michael Stringer in The Journal of the Proactive Directive Dichotomy (20174U)

structure plan.

To make the main points more explicit, it is fair to say that;
  * the value of the optical continuous reconstruction is reciprocated by what should be termed the sanctioned major issue.
  * The core drivers poses problems and challenges for both the heuristic non-referent spirituality and any discrete or Philosophical configuration mode.
  * an anticipation of the effects of any interpersonal fragmentation reinforces the weaknesses in the explicit deterministic service. This may be due to a lack of a doctrine of the interpersonal quality..
  * any significant enhancements in the strategic plan probably expresses the strategic personal theme. This trend may dissipate due to the personal milieu.

 firm assumptions about ideal major monologism evinces the universe of attitude.

The Flexible Implicit Aspiration.

Within current constraints on manpower resources, any consideration of the lessons learnt can fully utilize what should be termed the two-phase multi-media program.

For example, the assertion of the importance of the integration of doctrine of the prime remediation with strategic initiatives cannot be shown to be relevant. This is in contrast to the strategic fit.

To learn more about Bogus head over to the documentation.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE: