Abstract test cases for enforcing contracts

Often when programming, we must guard against invalid inputs that can come from users, databases and web services.

Guarding against these inputs can seem like a lot work, and that there’s more important tests that we should be focusing on.

Note: Boundary testing is very important so don’t skimp on it! Plus, these types are test are very easy to write.

We’ll there’s good news: In some cases, we can leverage abstract test cases to reduce the cost of writing these checks.

The cases where this works extremely well is where we have an abstraction, with many implementers. An example of such a system is a FileUploader abstraction: The goal is to upload a file, but there are many different ways to do it.

public interface IFileUploader
{
    void Upload(string path, object contents);
}
public class S3FileUploader : IFileUploader
{
    public void Upload(string path, object contents)
    {
        // Perform upload to Amazon S3
    }
}
public class FTPFileUploader : IFileUploader
{
        public void Upload(string path, object contents)
        {
            // Perform an FTP upload
        }
}

In this case, we need to do a lot of parameter checking. In addition, If I ever decide to support SFTP uploads as well, I’d need to create even more tests to check those parameters as well; even though the code to test them would be pretty much the same!

To avoid all of that work, we could put those checks into an abstract test case to reuse them. The abstract test case would contain all of tests to guard against invalid values.

public abstract class FileUploaderAbstractTestCase
{
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    public void UploadWithNullOrEmptyPathThrowsException(string path)
    {
        // Fixture setup
        var sut = CreateSut();

        // Exercise system
        var exception = Record.Exception(() => sut.Upload(path, new object()));

        // Verify outcome
        AssertException<ArgumentException>("Path cannot be null or empty", exception);

        // Teardown
    }

    [Fact]
    public void UploadWithNullContentThrowsException()
    {
        // Fixture setup
        var sut = CreateSut();

        // Exercise system
        var exception = Record.Exception(() => sut.Upload("validPath", null));

        // Verify outcome
        AssertException<ArgumentException>("Content cannot be null", exception);

        // Teardown
    }    

    public static void AssertException<T>(string expectedMessage, Exception actualException)
    {
            Assert.IsType<T>(actualException);
            Assert.Equal(expectedMessage, actualException.Message);
    }

    public abstract IFileUploader CreateSut();
}

Now we can use those tests for each specific class implementing that behavior to ensure that they act appropriately.

public class S3FileUploaderTestCase : FileUploaderAbstractTestCase
{
    public override IFileUploader CreateSut()
    {
        return new S3FileUploader();
    }
}

public class FTPFileUploaderTestCase : FileUploaderAbstractTestCase
{
    public override IFileUploader CreateSut()
    {
        return new FTPFileUploader();
    }
}

Now, when a new SFTP file uploader is created, it will be a matter of sub classing from our abstract test case to ensure that the SFTP file uploader upholds the contract for guarding against these invalid inputs.

Of course, this approach doesn’t only apply to gaurding against invalid inputs, but could be use to verify the behavior of each file uploader as a whole.

New tests for “When upload is called, with given path and contents, will upload file” could be added to ensure that each uploader performs some upload action with the path and content information.

This would ensure each implementer properly implements the contract for that particular abstraction.

Unit testing queries with Entity Framework

When working with the entity framework, sometimes you’d just like to test the output of your linq to sql statements without invoking the database.

In the past, I’ve tried using fake, in-memory dbsets to test these queries but the resulting test doubles always felt clunky.

You would end up creating a header interface for the DbContext which is a pain to maintain when you have many dbsets in one dbcontext.

public class DatabaseContext : DbContext, IDatabaseContext
{
	DbSet<Customer> Customers { get; set; }
	// 26 other sets...
}

public interface IDatabaseContext :
{
	DbSet<Customer> Customers { get; set; }
	// 26 other sets...
}

public class TestWebContext : IDatabaseContext
{
	DbSet<Customer> Customers { get; set; } = new TestDbSet<Customer> // Fake, In Memory DB Set
	// 26 other sets...

	public override int SaveChanges() => 0;
}

Instead, what I propose is following Jimmy’s advice and to use queries instead. His article can be found here: https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

In this case, we should prefer query objects over repositories, limiting the number of entity framework related things you have to mock for a single test.

With that in mind we can more easily test these queries by using the .NET Framework’s built in ienumerable classes without much hassle.

public class CustomerQuery
{
	public string Name { get; set; }

	public Expression<Func<Customer, bool>> AsExpression()
	{
		return c => c.Name == Name;
	}

	public IEnumerable<Customer> Execute(IQueryable<Customer> customers)
	{
		return customers.Where(AsExpression).ToList();
	}
}

With all that in place, we can now test the output of linq to sql expressions.

public class CustomerQueryTest
{
	[Fact]
	public void WithMatchingCustomerNameWillReturnCustomer()
	{
		// fixture setup
		var customers = new List<Customer>
		{
			new Customer { Name = "Joe" }
		}

		var sut = new CustomerQuery();

		// exercise system
		var results = sut.Execute(customers.AsQueryable());

		// verify outcome
		Assert.Equal(1, results.Count());

		// teardown
	}
}

Finally, you can use the new query by passing the dbset directly to the query object’s execute method.

public ActionResult DisplayCustomers(string customerName)
{
	var model = new DisplayCustomersViewModel();

	using(var dbContext = new DatabaseContext())
	{
		var query = new CustomerQuery();
		var customers = query.Execute(dbContext.Customers);
		model = CustomerViewModelMapper.Map(customers);
	}

	return View(DisplayCustomersViewModel);
}

This definitely makes testing the logic much simpler without having to stub the whole dbcontext.

Sadly, this doesn’t guarantee that your linq to sql statements will work correctly against a real database but this will definitely help with testing code containing linq to sql expressions.

Unit Testing Naming Conventions

Test Naming Conventions

When working with open source projects, you run across a few different ways to name your unit tests.

Let’s take a look at a few of them by example testing a simple calculator with one operation: Divide

public class Calculator
{
	public void Divide(int x, int y) => x / y;
}

Test Method Naming Conventions

With/Will

DivideWithDivisorEqualZeroWillThrowDivideByZeroException()

Given/Should

DivideGivenAZeroDivisorShouldThrowDivideByZeroException()

Given/When/Then

GivenAZeroDivisor_WhenDivideIsCalled_ThenDivideByZeroExceptionIsThrown.

Fixture naming conventions

Test class per method

public class CalculatorTests
{
	public class Divide
	{
		[Fact] DivideWithZeroDivisorThrowsDivideByZeroException() { /* ... */ }	
	}
}

When - Should fixture

public class WhenDividingByZero
{
	[Fact] ShouldThrowDivideByZeroException() { /**/ }
}

Hopefully I can expand upon this list when a few more examples come to mind!

Using NUnit with Visual Studio's Test Explorer

Currently there’s a problem with the 3.0+ versions of NUnit using the NUnitTestAdapter where tests are not added to Visual Studio’s test explorer on build. We can work around this by using NUnit 2.6.4 along with the NUnitTestAdapter 2.0 so that they are compatible with each other:

1: Install NUnit with NUGET package manager

image

2: Install the NUnitTestAdapter

image

Now since both versions of NUnit and the adapter match, Visual Studio shouldn’t have any problems find the tests automagically now!

Someone already posted a solution for this but I figured I would summarize it in a quick post.