DEV Community

loading...
Cover image for The Essential Unit Testing Techniques, Part 1

The Essential Unit Testing Techniques, Part 1

Mohamed Halawa
Software Engineer
ใƒปUpdated on ใƒป5 min read

Unit testing plays a vital role in the software development process, but how do we actually test our code?

In the following article I'll try to summarize and mention various techniques followed by .NET Developer using the NUnit library.

Testing strings

Testing against specific strings is not a robust way of testing strings, For example

Assert.That(result, Is.EqualTo("<strong>abc</strong>"));
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘ As a rule of thumb, testing strings is recommended to be more general than if it was too specific it would fail with every tiny change!

๐Ÿ‘ Strings are all about format! so any method that can test the format for you is a viable testing method!

  • Using string interpolation $"{}" or string.Format()
  • Using Does.StartWith, Does.EndWith, Does.Contain methods provided by NUnit
  • Using Regex (more advanced)

1. Using $"{}" format or string.Format

You can easily check the targeted format using string.format or $"{}" along with Assert.That(result,Is.EqualTo()), for example

Assert.That(result, Is.EqualTo($"<strong>{str}</strong>"));
Enter fullscreen mode Exit fullscreen mode

2. Using Does.StartWith, Does.EndWith, Does.Contain Methods

You can also use the fellow methods to check if a string is enclosed between specific words or tags!

Assert.That(result, Does.StartWith("<strong>"));
Assert.That(result, Does.EndWith("</strong>"));
Assert.That(result, Does.Contain($"{str}"));
Enter fullscreen mode Exit fullscreen mode

3. Using Regex

Regex is the god-level matching mechanism for strings! and fortunately, you can use it with NUnit to test your strings

Assert.That(result, Does.Match("<pattern>"))
Enter fullscreen mode Exit fullscreen mode

Testing arrays and collections

Testing arrays or collections is a little bit tricky since there are many factors you can test

1. Testing if an Array has any elements

Assert.That(result,Is.Not.Empty)
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”˜ Yet, it's a general test case!

โ“How can you make sure if it has a certain count of elements?

2. Testing the count of a collection

 Assert.That(result.Count(),Is.EqualtTo(<num>))
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”˜ It's also a general test case!

โ“How can you make sure if it has a certain element?!

3. Testing if a collection contains X

Assert.That(result, Does.Contain(1));
Assert.That(result, Does.Contain(3));
Assert.That(result, Does.Contain(5));
Enter fullscreen mode Exit fullscreen mode

๐Ÿค• It brings more headache due to the repetitive work!

โšก Fortunately, there is another way to do just the same!

Assert.That(result, Is.EquivalentTo(new [] {1,2,3})
Enter fullscreen mode Exit fullscreen mode

โ‰๏ธ What about the order of it's elements!

4. Testing the order of a collection

Assert.That(result,Is.Ordered);
Enter fullscreen mode Exit fullscreen mode

โ‰๏ธ And What about the uniqueness of its elements!

5. Testing the uniqueness of a collection's elements!

Assert.That(result,Is.Unique);
Enter fullscreen mode Exit fullscreen mode

Testing Methods

Like arrays, there are also many factors that you consider when testing a method or a function!

  • return type
  • return value
  • void methods
  • exceptions are thrown by methods
  • events being raised by a method
  • private methods

Testing return type

NUnit provides two different ways to test the return type of a method or a function.

  1. Is.TypeOf<>()
  2. Is.InstanceOf<>()

Is.TypeOf<>()

Checks if an instance (object or value) is of a specific type and just that type. for example:

Assert.That(result,Is.TypeOf<NotFound>());
Enter fullscreen mode Exit fullscreen mode

Is.InstanceOf<>()

Checks if an instance (object or value) is of a specific type or one of its derivatives. for example:

Assert.That(result,Is.InstanceOf<NotFound>());
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—’๏ธ It also provides the non-generic version of both methods, that can be used along with typeof

Testing void methods (commands)

Void methods or commands are trickier to test than anything else because they

  1. change a state of an object either in memory, database
  2. persist a state, they may store an object in a database or call a web service or a message queue.

Unfortunately, the only way to accomplish such a task is to preserve the state of the object being affected by that method. For example

public class ErrorLogger
{
    public string LastError { get; set; }

    public event EventHandler<Guid> ErrorLogged; 

    public void Log(string error)
    {
        if (String.IsNullOrWhiteSpace(error))
            throw new ArgumentNullException();

        LastError = error; 

        // Write the log to a storage
        // ...

        ErrorLogged?.Invoke(this, Guid.NewGuid());
    }
}
Enter fullscreen mode Exit fullscreen mode
[Test]
[TestCase("Something went wrong")]
public void Log_WhenStringIsNotNullOrWhiteSpace_ThrowAnException(string message)
{
    _logger.Log(message);
    Assert.That(_logger.LastError,Is.EqualTo(message));
}
Enter fullscreen mode Exit fullscreen mode

Testing methods that throw an exception

NUnit provides a way to test if a method throws a specific exception and no matter it's a built-in exception or custom exception you can always test it!

But... you cannot just call your method in a normal way and expecting the Assert to detect that exception rather you've to call it as a delegate within the Assert.That method.

So, Instead of

[Test]
[TestCase("")]
[TestCase(" ")]
[TestCase(null)]
public void Log_InvalidErrorMessage_ThrowArgumentNullException(string message)
{
    _logger.Log(message); // Execution will be stopped as a result for the exception and the test will fail!
    Assert.That(_logger.LastError,Throws.ArgumentNullException);
}
Enter fullscreen mode Exit fullscreen mode

Do the following

[Test]
[TestCase("")]
[TestCase(" ")]
[TestCase(null)]
public void Log_InvalidErrorMessage_ThrowArgumentNullException(string message)
{
    Assert.That(() =>
    {
        _logger.Log(message);
    },Throws.ArgumentNullException);
}
Enter fullscreen mode Exit fullscreen mode

If you notice, I've used the Throws.ArgumentNullException property to assert the result and that is a built-in exception.

โ“What about the Custom Exceptions?

๐Ÿ’ฌ Well, you can use the Throws.Exception.TypeOf<>() method just for that!

Testing methods that raise an event

Testing events is nothing more than testing if it was raised for a specific scenario or not, so what we gonna really need is a simple indicator to answer our question. For Example

public class ErrorLogger
{
    public string LastError { get; set; }

    public event EventHandler<Guid> ErrorLogged; 

    public void Log(string error)
    {
        if (String.IsNullOrWhiteSpace(error))
            throw new ArgumentNullException();

        LastError = error; 

        // Write the log to a storage
        // ...

        ErrorLogged?.Invoke(this, Guid.NewGuid());
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, our event will send an argument which is Guid.NewGuid() which we can assume to be the indicator that will tell us if it was raised or not.

[Test]
[TestCase("Something went wrong")]
public void Log_ValidError_RaiseErrorLoggedEvent(string messge)
{
    // Arrange
    var id = Guid.Empty;

        // Subscribe to the event
    _logger.ErrorLogged += (sender, arg) => { id = arg; };

    // Act
    _logger.Log(messge);

    // Assert
    Assert.That(id, Is.Not.EqualTo(Guid.Empty));
}
Enter fullscreen mode Exit fullscreen mode
  • So, to test the event, we are going to subscribe to it first
  • Assign the passed Guid to a local variable, that was previously initialized with a default value.
  • Finally, we can assert that the Id is not the same as its default value! which means that event has already been raised and mutated the local variable.

Testing private methods

โ‰๏ธ How to test the private or protected methods?

๐Ÿ’ญ You shouldn't test private methods! we are only testing the API of a class (aka public members)

โ” Does it mean that we should test against Interfaces?

Reasons

  • Private methods are participating to be an actual part of the implementation details of a class, which can be changed at anytime from one version to another. but public members would stay the same!
  • Hence, if you tested against private members your tests will be coupled with implementation detail.

Testing Private Methods


Code Coverage

โ‰๏ธ How do we know if we have enough tests?

Well, you can make use of a code coverage tool that is mainly were designed to

  • Scale your code
  • Answer that question by telling what parts of our code that's not fully covered by tests

Available Tools

  • Visual Studio Enterprise Edition

  • Resharper Ultimate, Dot Cover

Dot Cover is a unit test runner

  • NCover

Resources

Discussion (0)