Both xUnit and Expecto have very different approaches when it comes to describing tests. While xUnit is a classic implementation and a modern version of the older original NUnit approach, Expecto brings a whole new flexibility to testing. Migrating from one to the other is rather straightforward in some parts, but can involve a great mind shift in others.
Expecto is purely written in F#, so there’s a language barrier, but that should not hold you back from looking for improvements in your test suite. The following sections describe the differences between simple and data-driven tests. Note that this is by no means a complete guide, but should already get you started fairly well.
In Expecto, everything is a Test
type. It does not matter if the test is synchronous, asynchronous, if it uses test fixtures, data or is a property-based test. It all results in a single common Test
type. This will already give you an idea of reusability and flexibility when it comes to writing and changing tests.
Fact to test
The xUnit testing framework describes single sync/async test cases with the [Fact]
attribute. These tests are the most common and most simple tests described with this framework. However, because of their restrictions (need of an attribute), they are not reusable by default.
An example from xUnit itself:
xUnit [Fact]
s are (almost) directly transferable to Expecto’s test
s.
The Assert
to Expect
migration should be rather straightforward as it is almost an exact translation between the two. Otherwise, the biggest difference is the usage of the test
computation builder, but we can also use testCase
as a regular function.
💡 Note that Expecto also supports directly property-based testing, which is less verbose and defined with testProperty
function rather than with FsCheck’s own [Property]
attribute for xUnit environments.
Theory to list of test
xUnit describes a test as a [Theory]
when the inputs are passed. While [InlineData]
, [MemberData]
and [ClassData]
can be a good approach to simple inputs, it can quickly become over-complicated when the inputs start to change or expand.
💡 Sometimes, when the inputs are that simple and do not represent any enumeration value, the entire test could better be described as a property-based test.
Here is the example that xUnit uses to explain theories:
To map this as-is to Expecto, we could generate several tests based on these inputs. Note that in the Expecto example, we are no longer limited to the constant expression that .NET attributes have. The inputs of the tests generated here could be anything. Instead of reinventing, we can simply reuse existing language features — in this case looping. This rewires your thinking, as a ‘theory’ is just several tests, which are more explicit and powerful in the Expecto example.
Fixture to function
xUnit has several tricks to share a context between tests, ranging from class to the assembly fixture interfaces you can use. As you only have those two scopes (class and assembly), you are forced to group your tests by class. This is actually a very common practice, where test classes are almost a mirror of the class it tests. Because of this structure, common functionality is often hidden and duplication can occur.
👀 Notice how many infrastructure classes you have to create to define a test fixture that is used across classes. That is because we are limited to the scope of classes.
Expecto breaks this by being able to pass in your own list of tests. If you want a singleton test fixture (= xUnit collection fixture), you can pass it in like any other argument:
If you want to create a new fixture for every test run, you can use Expecto’s testFixture(Async)
:
Conclusion
Expecto uses a whole different approach when it comes to writing tests. It breaks the classic barriers of SUT class Test class and instead focuses on tests itself. It could mean that we need common functionality that is not showing in the SUT class, but because each xUnit test class is a flawed mirror we cannot fully reuse functionality like we want to without over-complicate things.
Expecto lets you have as many test fixtures and generative inputs as you want, without the testing framework holding you back.
I wrote a dedicated article about this amazing freedom you have when writing tests in Expecto.
Thanks for reading!
Stijn
Subscribe to our RSS feed