Mohamed Halawa
Posted on July 29, 2021
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>"));
๐ 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
$"{}"
orstring.Format()
- Using
Does.StartWith
,Does.EndWith
,Does.Contain
methods provided byNUnit
- 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>"));
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}"));
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>"))
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)
๐ 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>))
๐ 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));
๐ค 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})
โ๏ธ What about the order of it's elements!
4. Testing the order of a collection
Assert.That(result,Is.Ordered);
โ๏ธ And What about the uniqueness of its elements!
5. Testing the uniqueness of a collection's elements!
Assert.That(result,Is.Unique);
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.
Is.TypeOf<>()
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>());
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>());
๐๏ธ 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
- change a state of an object either in memory, database
- 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());
}
}
[Test]
[TestCase("Something went wrong")]
public void Log_WhenStringIsNotNullOrWhiteSpace_ThrowAnException(string message)
{
_logger.Log(message);
Assert.That(_logger.LastError,Is.EqualTo(message));
}
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);
}
Do the following
[Test]
[TestCase("")]
[TestCase(" ")]
[TestCase(null)]
public void Log_InvalidErrorMessage_ThrowArgumentNullException(string message)
{
Assert.That(() =>
{
_logger.Log(message);
},Throws.ArgumentNullException);
}
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());
}
}
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));
}
- 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.
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
Posted on July 29, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.