Over the last couple of months, I have been woking on FSpec, a test framework for .NET, heavily inspired by RSpec.
FSpec at GitHub (More comprehensive documentation found there)
There already exist a couple of test frameworks for the .NET platform, taking a similar approach to testing as RSpec - I am aware of NSpec and MSpec. But where RSpec (and Ruby in general) excels in creating beautiful DSLs, I feel that NSpec and MSpec have not accomplished this.
FSpec takes a different approach. It is written in and for F#, i.e. the test code is written in F#. But the system under test can be implemented in any language targeting the .NET framework.
My original intention was to create a test framework with a cleaner syntax for my C# projects.
Try to see how FSpec compares to other test frameworks
FSpec became self-testing in its 14th commit! I.e. although the form of FSpec have changed significantly since then, the unit tests for FSpec itself have been executed by FSpec's own run function since it's 14th commit.
If you have a development team working with a different language on the .NET platform, FSpec could be a great low-risk way of introducing F#.
Currently the following features are supported
IDisposableinstances in context
Lets walk through an example
let specs = describe "CreateUserFeature" [ before (fun ctx -> // Retrieve email and existing user from example metadata. let email = ctx.metadata?email let userMock = Mock<IUserRepository>() .Setup(fun x -> <@ x.FindByEmail(email) @>) .Returns(ctx.metadata?existing_user) .Create() let input = CreateUserInput ( Email = email ) // The Inject and CreateUser functions are not part of FSpec // but specific tests can easily add extensions to the context ctx.Inject userMock ctx.CreateUser input ) // Setup a collection of metadata. These values will be valid for // all examples in that context. // The operators are a bit strange, improvement suggestions are // welcome ("email" ++ "firstname.lastname@example.org" ||| "existing_user" ++ null) ==> context "when a user doesn't exist" [ it "creates a new user" (fun ctx -> let repo = ctx.GetMock<IUserRepository>() let expectedUser = fun (u:User) -> u.Email = "email@example.com" verify <@ repo.Save(It.is(expectedUser)) @> once ) it "reports success" (fun ctx -> let callback = ctx.GetMock<ICreateUserCallback>() verify <@ callback.UserCreated() @> once ) // We could rely on the email still being setup according to // the meta data specified on the context. By I like that the // data that is important for the outcome of a specific test // is very visible in that test. ("email" ++ "firstname.lastname@example.org") ==> it "sends an email to the user" (fun ctx -> ctx.verifyMailSentToUser "email@example.com" ) it "sends only one mail" (fun ctx -> let mailer = ctx.GetMock<IMailer>() verify <@ mailer.SendMailMessage(any()) @> once ) context "password encryption" [ // Pending examples will show up in the test output clearly // marked as "PENDING", but will not fail the test it "creates an encrypted password and salt" pending it "creates a unique password and salt each time" pending it "can validate the password after creation" pending ] ] ("email" ++ "firstname.lastname@example.org" ||| "existing_user" ++ (create_a_user())) ==> context "when the email is already registered" [ it "does not create a new user" (fun ctx -> let userRepository = ctx.GetMock<IUserRepository>() verify <@ userRepository.Save(any()) @> never ) it "reports duplicate user" (fun ctx -> let callback = ctx.GetMock<ICreateUserCallback>() verify <@ callback.UserAlreadyExists() @> once ) ] ]
Running the specs result in the following output:
One thing worthy of note if how meta data applied to a specific example, or group of examples can be accessed in the general setup, resulting in less code duplication than other test frameworks on the .NET platform that I'm familiar with.
This post is just meant as an introduction to FSpec. Over time, I will try to write more articles describing how to use the tool.
There is better documentation of the tool in the readme on the github page, so I invite everybody to check it out.