Friday, October 31, 2008

Mocking F#

The intent of this entry is to show how to do TDD (Test Driven Development) in F# with the help of a dynamic mock object framework such as RhinoMocks. Along the way, we will explore how to implement an object oriented design in F# with features including namespaces, classes, interfaces, properties, and members. This example includes several best practices, but is by no means production ready. Most specifically, it is missing error handling and validation. In addition, certain patterns and practices have been intentionally ignored in order to make the example a little easier to understand.

This is an implementation of a simplified customer service that might exist in any standard LOB (Line of Business) application. In the end, we have a service that allows the current balance of a customer to be returned with a specified discount applied.

As always, we write the tests first (Note: As is my usual style, the tests are in C# and the code is in F#):

C# Customer Entity Test:

[TestMethod]
public void CanCreateCustomer()
{
FSharpMockExample.Entities.ICustomer customer = new FSharpMockExample.Entities.Customer(1, "ABC, Corp.", 20);
Assert.AreEqual(customer.Id, 1);
Assert.AreEqual(customer.Name, "ABC, Corp.");
}

[TestMethod]
public void CanCalculateBalanceWith10PercentDiscount()
{
FSharpMockExample.Entities.ICustomer customer = new FSharpMockExample.Entities.Customer(1, "ABC, Corp.", 20);
decimal newBalance = customer.CalculateBalanceWithDiscount(.1M);
Assert.AreEqual(newBalance, 18);
}



F# Customer Entity:




Signature File:



#light
namespace
FSharpMockExample.Entities

type ICustomer = interface
abstract
Id: int with get
abstract Name: string with get
abstract CalculateBalanceWithDiscount: decimal -> decimal
end

type
Customer = class
new
: int*string*decimal -> Customer
interface ICustomer
end



Source File:



#light
namespace
FSharpMockExample.Entities

type ICustomer = interface
abstract
Id: int with get
abstract Name: string with get
abstract CalculateBalanceWithDiscount: decimal -> decimal
end

type
Customer = class
val
id: int
val name: string
val balance: decimal
new(id, name, balance) =
{id = id; name = name; balance = balance}
interface ICustomer with
member
this.Id
with get () = this.id
member this.Name
with get () = this.name
member this.CalculateBalanceWithDiscount discount =
this.balance - (discount * this.balance)
end
end



C# Customer Data Access Object Test:



[TestMethod]
public void CanGetCustomerById()
{
FSharpMockExample.Data.ICustomerDao customerDao = new FSharpMockExample.Data.CustomerDao();
FSharpMockExample.Entities.ICustomer customer = customerDao.GetById(1);
Assert.AreEqual(customer.Id, 1);
Assert.AreEqual(customer.Name, "ABC Company");
}



F# Customer Data Access Object:



Signature File:



#light
namespace
FSharpMockExample.Data
open FSharpMockExample.Entities

type ICustomerDao = interface
abstract
GetById: int -> ICustomer
end

type
CustomerDao = class
new
: unit -> CustomerDao
interface ICustomerDao
end



Source File:



#light
namespace
FSharpMockExample.Data
open FSharpMockExample.Entities

type ICustomerDao = interface
abstract
GetById: int -> ICustomer
end

type
CustomerDao = class
new
()={}
interface ICustomerDao with
member
this.GetById id =
new Customer(id, "ABC Company", 20.00M) :> ICustomer
end



C# Customer Service Test:



[TestMethod]
public void CanCalculateBalance()
{
var mocks = new MockRepository();
var customerDao = mocks.CreateMock<FSharpMockExample.Data.ICustomerDao>();
FSharpMockExample.Entities.ICustomer customer = new FSharpMockExample.Entities.Customer(1, "XYZ Company", 50);

using (mocks.Record())
{
Expect.Call(customerDao.GetById(1)).IgnoreArguments().Return(customer);
}

using (mocks.Playback())
{
int customerId = 1;
decimal discount = .1M;
FSharpMockExample.Services.ICustomerService customerService = new FSharpMockExample.Services.CustomerService(customerDao);
var balanceWithDiscount = customerService.CalculateBalaceWithDiscount(customerId, discount);

Assert.AreEqual(balanceWithDiscount, 45);
}
}



F# Customer Service:



Signature File:



#light
namespace
FSharpMockExample.Services
open FSharpMockExample.Data
open FSharpMockExample.Entities

type ICustomerService = interface
abstract
CalculateBalaceWithDiscount: int*decimal -> decimal
end

type
CustomerService = class
new
: unit -> CustomerService
new: ICustomerDao -> CustomerService
end



Source File:



#light
namespace
FSharpMockExample.Services
open FSharpMockExample.Data
open FSharpMockExample.Entities

type ICustomerService = interface
abstract
CalculateBalaceWithDiscount: int*decimal -> decimal
end

type
CustomerService = class
val
customerDao: ICustomerDao
new (customerDao) =
{customerDao = customerDao}
new () =
{customerDao = new CustomerDao()}
interface ICustomerService with
member
this.CalculateBalaceWithDiscount (customerId, discount) =
let customer =
this.customerDao.GetById(customerId)
customer.CalculateBalanceWithDiscount(discount)
end
end






Hopefully, this example will inspire you to go out an give this amazing language a try.

1 comment:

  1. I am inspired! :) I'd like to see this used w/ MVC & some crazy view... like silverlight or NHaml.

    ReplyDelete