FluentAssertions is an alternative assertion library for unit tests, to use instead of the methods in Assert class that Microsoft provides. It has much better support for exceptions and some other stuff that improves readability and makes it easier to produce tests.
The coding of Kentor.AuthServices was a perfect opportunity for me to do some real TDD (Test Driven Development) again. I have long thought that the
[ExpectedException] attribute that MsTest offers is not enough, so when Albin Sunnanbo suggested that I’d look at FluentAssertions I decided to try it.
FluentAssertions offers a
ShouldThrow() extension method to the
Action delegate type. It asserts that invoking a particular action will throw an exception.
// Code from https://github.com/KentorIT/authservices/blob/master/
Action a = () => Saml2Response.Read(response).GetClaims();
.WithMessage("The Saml2Response must be validated first.");
Compared to the
[ExpectedException] attribute this offers much better control.
Properties with non public setters sometimes make sense – until a unit test requires an object to be set up in a specific state. I don’t want to make the setter public just because of the tests, so I wrote a small helper function that can call non public setters.
The helper returns the object itself, to allow a fluent syntax inside member initialization expressions and uses a lambda to select the member (I do like compile time checking whenever it’s possible).
This is actual code I wrote last week using the helper. It’s part of a set up of an object collection used for unit tests.
Orders = new List<Order>
OrderId = 983427,
}.CallNonPublicSetter(o => o.StatusId, OrderStatus.Confirmed),
OrderId = 18956,
}.CallNonPublicSetter(o => o.StatusId, OrderStatus.Delivered)
The helper is implemented using reflection to access the property to bypass the protection level of the setter.
In a perfect Scrum world, the team tests everything themselves. I think that misses an important point – the developers have a code-centric view on the domain. Good testing requires a user- or business-centric view on the domain. I think that it is impossible to both have a deep understanding of the code and to be a good tester.
That doesn’t relieve the developers from tests – developers having any pride in what they do of course unit test all their code. To get high quality software unit tests (whether automated or not, I’ll leave that discussion outside this post) is important, but not alone sufficient. There have to be system level tests and user/acceptance tests too.
Recently I was involved in a problem where we had a WCF service referencing a 32 bit dll. The service was set to to “Start WCF Service Host when debugging another project in the same solution”. Unfortunately that ended up with an exception.
Could not load file or assembly ‘My32BitLib, Version=126.96.36.199, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. An attempt was made to load a program with an incorrect format.
When a CLR process starts, the exe file determines if it will be loaded as a 32 or 64-bit process. In this case the
WcfSvcHost.exe is started as a 64-bit process, loads the service dll (compiled as “Any CPU”) and then fails when trying to load the My32BitLib assembly which is compiled as “x86”.
The solution is to create a special 32bit version of
WcfSvcHost and setup the debugging environment to use that instead of the standard
Unit tests should preferably be independent of external services, systems and files. The standard way to achieve this is to create mocks. A mock is an object that can be used in place of the real resource and act in a predictable way to ensure the tests always give the same result. I think that this is a perfectly valid approach for any external services that the system is integrated with. The problem for business applications is the database. Mocking the database by providing a mock implementation of the database access layer is a huge task. It is too huge to be worth the effort. Another approach that I often use is to use a database in the tests, but keep it unchanged through transactions.