Look, no Mocks! Functional TDD with F# Mark Seemann @ploeh.

65
Look, no Mocks! Functional TDD with F# Mark Seemann http://blog.ploeh.dk @ploeh

Transcript of Look, no Mocks! Functional TDD with F# Mark Seemann @ploeh.

Page 1: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Look, no Mocks! Functional TDD with F#

Mark Seemannhttp://blog.ploeh.dk

@ploeh

Page 2: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD is dead!

http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html

Page 3: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Is TDD dead?

Page 4: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

A system you can’t test

Page 5: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Detestable

http://martinfowler.com/bliki/Detestable.html

Page 6: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

Page 7: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation

Page 8: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {

Page 9: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,

Page 10: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,

Page 11: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,

Page 12: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity

Page 13: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });

Page 14: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });        ctx.SaveChanges();

Page 15: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });        ctx.SaveChanges();    }

Page 16: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });        ctx.SaveChanges();    }

Page 17: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });        ctx.SaveChanges();    }

    return this.Ok();

Page 18: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public IHttpActionResult Post(ReservationRendition rendition){    DateTimeOffset requestedDate;    if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate))        return this.BadRequest("Invalid date.");

    var min = requestedDate.Date;    var max = requestedDate.Date.AddDays(1);

    using (var ctx = new ReservationsContext())    {        var reservedSeats = (from r in ctx.Reservations                             where min <= r.Date && r.Date < max                             select r.Quantity)                            .DefaultIfEmpty(0)                            .Sum();        if (rendition.Quantity + reservedSeats > capacity)            return this.StatusCode(HttpStatusCode.Forbidden);

        ctx.Reservations.Add(new Reservation        {            Date = requestedDate,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        });        ctx.SaveChanges();    }

    return this.Ok();}

Page 19: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.
Page 20: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Test-Induced Damage

Page 21: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public class ReservationsController : ApiController{    private readonly IApiValidator apiValidator;    private readonly IReservationRepository repository;    private readonly IMaîtreD maîtreD;    private readonly IReservationMapper mapper;

    public ReservationsController(        IApiValidator apiValidator,        IReservationRepository repository,        IMaîtreD maîtreD,        IReservationMapper mapper)    {        this.apiValidator = apiValidator;        this.repository = repository;        this.maîtreD = maîtreD;        this.mapper = mapper;    }

    public IHttpActionResult Post(ReservationRendition rendition)    {        if (!this.apiValidator.Validate(rendition))            return this.BadRequest("Invalid date.");

        var r = this.mapper.Map(rendition);        var reservedSeats = this.repository.GetReservedSeats(r.Date);        if (!this.maîtreD.CanAccept(rendition.Quantity, reservedSeats))            return this.StatusCode(HttpStatusCode.Forbidden);

        this.repository.Save(r);

        return this.Ok();    }}

Page 22: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public interface IApiValidator{    bool Validate(ReservationRendition rendition);}

public interface IReservationMapper{    Reservation Map(ReservationRendition rendition);}

public interface IReservationRepository{    int GetReservedSeats(DateTimeOffset date);

    void Save(Reservation reservation);}

public interface IMaîtreD{    bool CanAccept(int requestedSeats, int reservedSeats);}

Page 23: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public interface IApiValidator{    bool Validate(ReservationRendition rendition);}

public interface IReservationMapper{    Reservation Map(ReservationRendition rendition);}

public interface IReservationRepository{    int GetReservedSeats(DateTimeOffset date);

    void Save(Reservation reservation);}

public interface IMaîtreD{    bool CanAccept(int requestedSeats, int reservedSeats);}

public class ApiValidator : IApiValidator{    public bool Validate(ReservationRendition rendition)    {        DateTimeOffset dummy;        return DateTimeOffset.TryParse(rendition.Date, out dummy);    }}

public class ReservationMapper : IReservationMapper{    public Reservation Map(ReservationRendition rendition)    {        return new Reservation        {            Date = DateTimeOffset.Parse(rendition.Date),            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };    }}

public class SqlReservationRepository : IReservationRepository, IDisposable{    private readonly ReservationsContext ctx;

    public SqlReservationRepository(ReservationsContext ctx)    {        this.ctx = ctx;    }

    public int GetReservedSeats(DateTimeOffset date)    {        var min = date.Date;        var max = date.Date.AddDays(1);

        return (from r in this.ctx.Reservations                where min <= r.Date && r.Date < max                select r.Quantity)                .DefaultIfEmpty(0)                .Sum();    }

    public void Save(DomainModel.Reservation reservation)    {        this.ctx.Reservations.Add(new Reservation        {            Date = reservation.Date,            Name = reservation.Name,            Email = reservation.Email,            Quantity = reservation.Quantity        });        this.ctx.SaveChanges();    }

    public void Dispose()    {        this.Dispose(true);        GC.SuppressFinalize(this);    }

    protected virtual void Dispose(bool disposing)    {        if (disposing)            this.ctx.Dispose();    }}

public class MaîtreD : IMaîtreD{    private int capacity;

    public MaîtreD(int capacity)    {        this.capacity = capacity;    }

    public bool CanAccept(int requestedSeats, int reservedSeats)    {        return requestedSeats + reservedSeats <= this.capacity;    }}

public partial class Reservation{    public int Id { get; set; }

    public DateTimeOffset Date { get; set; }

    [Required]    [StringLength(50)]    public string Name { get; set; }

    [Required]    [StringLength(50)]    public string Email { get; set; }

    public int Quantity { get; set; }}

public class Reservation{    public DateTimeOffset Date { get; set; }    public string Name { get; set; }    public string Email { get; set; }    public int Quantity { get; set; }}

public class CompositionRoot : IHttpControllerActivator{    public IHttpController Create(        HttpRequestMessage request,        HttpControllerDescriptor controllerDescriptor,        Type controllerType)    {        if(controllerType == typeof(ReservationsController))        {            var ctx = new ReservationsContext();            var repository = new SqlReservationRepository(ctx);            request.RegisterForDispose(ctx);            request.RegisterForDispose(repository);

            return new ReservationsController(                new ApiValidator(),                repository,                new MaîtreD(10),                new ReservationMapper());        }

        throw new ArgumentException(            "Unknown Controller type: " + controllerType,            "controllerType");    }}

Page 24: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public interface IApiValidator{    bool Validate(ReservationRendition rendition);}

public interface IReservationMapper{    Reservation Map(ReservationRendition rendition);}

public interface IReservationRepository{    int GetReservedSeats(DateTimeOffset date);

    void Save(Reservation reservation);}

public interface IMaîtreD{    bool CanAccept(int requestedSeats, int reservedSeats);}

public class ApiValidator : IApiValidator{    public bool Validate(ReservationRendition rendition)    {        DateTimeOffset dummy;        return DateTimeOffset.TryParse(rendition.Date, out dummy);    }}

public class ReservationMapper : IReservationMapper{    public Reservation Map(ReservationRendition rendition)    {        return new Reservation        {            Date = DateTimeOffset.Parse(rendition.Date),            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };    }}

public class SqlReservationRepository : IReservationRepository, IDisposable{    private readonly ReservationsContext ctx;

    public SqlReservationRepository(ReservationsContext ctx)    {        this.ctx = ctx;    }

    public int GetReservedSeats(DateTimeOffset date)    {        var min = date.Date;        var max = date.Date.AddDays(1);

        return (from r in this.ctx.Reservations                where min <= r.Date && r.Date < max                select r.Quantity)                .DefaultIfEmpty(0)                .Sum();    }

    public void Save(DomainModel.Reservation reservation)    {        this.ctx.Reservations.Add(new Reservation        {            Date = reservation.Date,            Name = reservation.Name,            Email = reservation.Email,            Quantity = reservation.Quantity        });        this.ctx.SaveChanges();    }

    public void Dispose()    {        this.Dispose(true);        GC.SuppressFinalize(this);    }

    protected virtual void Dispose(bool disposing)    {        if (disposing)            this.ctx.Dispose();    }}

public class MaîtreD : IMaîtreD{    private int capacity;

    public MaîtreD(int capacity)    {        this.capacity = capacity;    }

    public bool CanAccept(int requestedSeats, int reservedSeats)    {        return requestedSeats + reservedSeats <= this.capacity;    }}

public partial class Reservation{    public int Id { get; set; }

    public DateTimeOffset Date { get; set; }

    [Required]    [StringLength(50)]    public string Name { get; set; }

    [Required]    [StringLength(50)]    public string Email { get; set; }

    public int Quantity { get; set; }}

public class Reservation{    public DateTimeOffset Date { get; set; }    public string Name { get; set; }    public string Email { get; set; }    public int Quantity { get; set; }}

public class CompositionRoot : IHttpControllerActivator{    public IHttpController Create(        HttpRequestMessage request,        HttpControllerDescriptor controllerDescriptor,        Type controllerType)    {        if(controllerType == typeof(ReservationsController))        {            var ctx = new ReservationsContext();            var repository = new SqlReservationRepository(ctx);            request.RegisterForDispose(ctx);            request.RegisterForDispose(repository);

            return new ReservationsController(                new ApiValidator(),                repository,                new MaîtreD(10),                new ReservationMapper());        }

        throw new ArgumentException(            "Unknown Controller type: " + controllerType,            "controllerType");    }}

Page 25: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Unit Tests!

Page 26: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public class ApiValidatorTests{    [Fact]    public void ValidateReturnsTrueWhenRenditionHasValidDate()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 21, 17, 35),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Foo Baz",            Email = "[email protected]",            Quantity = 9        };        IApiValidator sut = new ApiValidator();

        var actual = sut.Validate(rendition);

        Assert.True(actual);    }

    [Fact]    public void ValidateReturnsFalseWhenRenditionHasInvalidDate()    {        var rendition = new ReservationRendition        {            Date = "Invalid date",            Name = "Foo Baz",            Email = "[email protected]",            Quantity = 9        };        IApiValidator sut = new ApiValidator();

        var actual = sut.Validate(rendition);

        Assert.False(actual);    }}

Page 27: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public class ReservationMapperTests{    [Fact]    public void MapReturnsCorrectResult()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 21, 25, 3),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Ndøh Ploeh",            Email = "ndø[email protected]",            Quantity = 6        };        IReservationMapper sut = new ReservationMapper();

        var actual = sut.Map(rendition);

        Assert.Equal(date, actual.Date);        Assert.Equal(rendition.Name, actual.Name);        Assert.Equal(rendition.Email, actual.Email);        Assert.Equal(rendition.Quantity, actual.Quantity);    }}

Page 28: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public class MaîtreDTests{    [Fact]    public void CanAcceptReturnsTrueForFirstRequestUnderCapacity()    {        var capacity = 10;        var sut = new MaîtreD(capacity);

        bool actual = sut.CanAccept(5, 0);

        Assert.True(actual);    }

    [Fact]    public void CanAcceptReturnsFalseForRequestAboveCapacity()    {        var capacity = 20;        var sut = new MaîtreD(capacity);

        var actual = sut.CanAccept(30, 0);

        Assert.False(actual);    }

    [Fact]    public void CanAcceptReturnsFalseForRequestAtCapacity()    {        var capacity = 20;        var sut = new MaîtreD(capacity);

        var actual = sut.CanAccept(capacity, 0);

        Assert.True(actual);    }

    [Fact]    public void CanAcceptReturnsFalseForRequestOverRemainingCapacity()    {        var capacity = 10;        var sut = new MaîtreD(capacity);

        var actual = sut.CanAccept(4, 7);

        Assert.False(actual);    }}

Page 29: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

public class ReservationsControllerTests{    [Fact]    public void SutIsApiController()    {        var sut =            new ReservationsController(                new Mock<IApiValidator>().Object,                new Mock<IReservationRepository>().Object,                new Mock<IMaîtreD>().Object,                new Mock<IReservationMapper>().Object);        Assert.IsAssignableFrom<ApiController>(sut);    }

    [Fact]    public void PostReturnsCorrectResponseForInvalidRendition()    {        var invalidRendition = new ReservationRendition        {            Date = "Not a valid date",            Name = "Foo Bar",            Email = "[email protected]",            Quantity = 3        };        var validatorStub = new Mock<IApiValidator>();        validatorStub            .Setup(v => v.Validate(invalidRendition))            .Returns(false);        var sut = new ReservationsController(            validatorStub.Object,            new Mock<IReservationRepository>().Object,            new Mock<IMaîtreD>().Object,            new Mock<IReservationMapper>().Object);

        IHttpActionResult actual = sut.Post(invalidRendition);

        Assert.IsAssignableFrom<BadRequestErrorMessageResult>(actual);    }

    [Fact]    public void PostReturnsCorrectResponseWhenThereAreNoReservedSeats()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 16, 10, 22),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Foo Bar",            Email = "[email protected]",            Quantity = 3        };        var validatorStub = new Mock<IApiValidator>();        validatorStub            .Setup(v => v.Validate(rendition))            .Returns(true);        var repositoryStub = new Mock<IReservationRepository>();        repositoryStub            .Setup(r => r.GetReservedSeats(date))            .Returns(0);        var maîtreDStub = new Mock<IMaîtreD>();        maîtreDStub            .Setup(m => m.CanAccept(rendition.Quantity, 0))            .Returns(true);        var mapperStub = new Mock<IReservationMapper>();        var reservation = new Reservation        {            Date = date,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };        mapperStub            .Setup(m => m.Map(rendition))            .Returns(reservation);        var sut = new ReservationsController(            validatorStub.Object,            repositoryStub.Object,            maîtreDStub.Object,            mapperStub.Object);

        var actual = sut.Post(rendition);

        Assert.IsAssignableFrom<OkResult>(actual);    }

    [Fact]    public void PostSavesReservationWhenMaîtreDCanAccept()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 20, 45, 42),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Fnaah Ploeh",            Email = "[email protected]",            Quantity = 2        };        var validatorStub = new Mock<IApiValidator>();        validatorStub            .Setup(v => v.Validate(rendition))            .Returns(true);        var repositoryMock = new Mock<IReservationRepository>();        repositoryMock            .Setup(r => r.GetReservedSeats(date))            .Returns(1);        var maîtreDStub = new Mock<IMaîtreD>();        maîtreDStub            .Setup(m => m.CanAccept(rendition.Quantity, 1))            .Returns(true);        var mapperStub = new Mock<IReservationMapper>();        var expected = new Reservation        {            Date = date,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };        mapperStub            .Setup(m => m.Map(rendition))            .Returns(expected);        var sut = new ReservationsController(            validatorStub.Object,            repositoryMock.Object,            maîtreDStub.Object,            mapperStub.Object);

        var actual = sut.Post(rendition);

        repositoryMock.Verify(r => r.Save(expected));    }

    [Fact]    public void PostDoesNotSaveReservationWhenMaîtreCannotAccept()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 20, 56, 57),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Fnaah Ploeh",            Email = "[email protected]",            Quantity = 2        };        var validatorStub = new Mock<IApiValidator>();        validatorStub            .Setup(v => v.Validate(rendition))            .Returns(true);        var repositoryMock = new Mock<IReservationRepository>();        repositoryMock            .Setup(r => r.GetReservedSeats(date))            .Returns(10);        var maîtreDStub = new Mock<IMaîtreD>();        maîtreDStub            .Setup(m => m.CanAccept(rendition.Quantity, 10))            .Returns(false);        var mapperStub = new Mock<IReservationMapper>();        var reservation = new Reservation        {            Date = date,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };        mapperStub            .Setup(m => m.Map(rendition))            .Returns(reservation);        var sut = new ReservationsController(            validatorStub.Object,            repositoryMock.Object,            maîtreDStub.Object,            mapperStub.Object);

        var actual = sut.Post(rendition);

        repositoryMock.Verify(            r => r.Save(It.IsAny<Reservation>()),            Times.Never());        var f = Assert.IsAssignableFrom<StatusCodeResult>(actual);        Assert.Equal(HttpStatusCode.Forbidden, f.StatusCode);    }}

Page 30: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

    [Fact]    public void PostDoesNotSaveReservationWhenMaîtreCannotAccept()    {        var date = new DateTimeOffset(            new DateTime(2014, 7, 27, 20, 56, 57),            TimeSpan.FromHours(2));        var rendition = new ReservationRendition        {            Date = date.ToString("o"),            Name = "Fnaah Ploeh",            Email = "[email protected]",            Quantity = 2        };        var validatorStub = new Mock<IApiValidator>();        validatorStub            .Setup(v => v.Validate(rendition))            .Returns(true);        var repositoryMock = new Mock<IReservationRepository>();        repositoryMock            .Setup(r => r.GetReservedSeats(date))            .Returns(10);        var maîtreDStub = new Mock<IMaîtreD>();        maîtreDStub            .Setup(m => m.CanAccept(rendition.Quantity, 10))            .Returns(false);        var mapperStub = new Mock<IReservationMapper>();        var reservation = new Reservation        {            Date = date,            Name = rendition.Name,            Email = rendition.Email,            Quantity = rendition.Quantity        };        mapperStub            .Setup(m => m.Map(rendition))            .Returns(reservation);        var sut = new ReservationsController(            validatorStub.Object,            repositoryMock.Object,            maîtreDStub.Object,            mapperStub.Object);

        var actual = sut.Post(rendition);

        repositoryMock.Verify(            r => r.Save(It.IsAny<Reservation>()),            Times.Never());        var f = Assert.IsAssignableFrom<StatusCodeResult>(actual);        Assert.Equal(HttpStatusCode.Forbidden, f.StatusCode);    }}

Page 31: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.
Page 32: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD

Test-Induced Damage

?

Page 33: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD

Test-Driven Design

Page 34: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Correlation

Page 35: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD

Test-Driven Design

Test-Induced Damage

Page 36: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD

Test-Driven Development

Page 37: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Functional Programming

Page 38: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Stubs Mocks

http://bit.ly/xunitpatterns

Page 39: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Stubs Mocks

Page 40: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Stubs Mocks

Page 41: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Stubs Mocks

http://bit.ly/growingoos

Queries Commands

Side-effectsNo side-effects

Page 42: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Stubs Mocks

Queries Commands

Side-effectsNo side-effects

F# is a Functional-First language

Page 43: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``Post returns correct result on success`` () =    let imp _ = Success ()    use sut = new ReservationsController(imp)    let rendition : ReservationRendition = {        Date = "2014-08-09+2:00"        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let result : IHttpActionResult = sut.Post rendition

    test <@ result :? Results.OkResult @>

type Result<'TSuccess,'TFailure> =     | Success of 'TSuccess    | Failure of 'TFailure

http://fsharpforfunandprofit.com/posts/recipe-part2

Page 44: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<CLIMutable>]type ReservationRendition = {    Date : string    Name : string    Email : string    Quantity : int }

type ReservationsController(imp) =    inherit ApiController()    member this.Post(rendition : ReservationRendition) =        this.Ok() :> IHttpActionResult

Page 45: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``Validate.reservation returns correct result on valid date`` () =    let rendition : ReservationRendition = {        Date = "2014-08-09+2:00"        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let actual = Validate.reservation rendition

    let expected = Success {        Date =            DateTimeOffset(DateTime(2014, 8, 9), TimeSpan.FromHours 2.)        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }    test <@ expected = actual @>

Page 46: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type Reservation = {    Date : DateTimeOffset    Name : string    Email : string    Quantity : int }

let reservation (rendition : ReservationRendition) = Success {    Date = rendition.Date |> DateTimeOffset.Parse    Name = rendition.Name    Email = rendition.Email    Quantity = rendition.Quantity }

Page 47: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``Validate.reservation returns right result on invalid date`` () =    let rendition : ReservationRendition = {        Date = "Not a date"        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let actual = Validate.reservation rendition

    let expected : Result<Reservation, Error> =        Failure(ValidationError("Invalid date."))    test <@ expected = actual @>

Page 48: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type Error =    | ValidationError of string

let reservation (rendition : ReservationRendition) =    match rendition.Date |> DateTimeOffset.TryParse with    | (true, date) -> Success {        Date = date        Name = rendition.Name        Email = rendition.Email        Quantity = rendition.Quantity }    | _ -> Failure(ValidationError "Invalid date.")

Page 49: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``check returns right result at no prior reservations`` () =    let capacity = 10    let getReservedSeats _ = 0    let reservation = {        Date =            DateTimeOffset(DateTime(2014, 8, 10), TimeSpan.FromHours 2.)        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let actual = Capacity.check capacity getReservedSeats reservation

    let expected : Result<Reservation, Error> = Success reservation    test <@ expected = actual @>

Page 50: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

let check capacity getReservedSeats reservation = Success reservation

Page 51: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``check returns right result at too little remaining capacity`` () =    let capacity = 10    let getReservedSeats _ = 7    let reservation = {        Date =            DateTimeOffset(DateTime(2014, 8, 10), TimeSpan.FromHours 2.)        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let actual = Capacity.check capacity getReservedSeats reservation

    let expected : Result<Reservation, Error> = Failure CapacityExceeded    test <@ expected = actual @>

Page 52: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type Error =    | ValidationError of string    | CapacityExceeded

let check capacity getReservedSeats reservation =    let reservedSeats = getReservedSeats reservation.Date    if capacity < reservation.Quantity + reservedSeats    then Failure CapacityExceeded    else Success reservation

Page 53: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``Post returns correct result on validation error`` () =    let imp _ = Failure(ValidationError("Invalid date."))    use sut = new ReservationsController(imp)    let rendition : ReservationRendition = {        Date = "2014-08-09+2:00"        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let result : IHttpActionResult = sut.Post rendition

    test <@ result :? Results.BadRequestErrorMessageResult @>

Page 54: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type ReservationsController    (        imp : ReservationRendition -> Result<unit, Error>    ) =    inherit ApiController()    member this.Post(rendition : ReservationRendition) =        match imp rendition with        | Failure(ValidationError msg) ->            this.BadRequest msg :> IHttpActionResult        | _ -> this.Ok() :> IHttpActionResult

Page 55: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

[<Fact>]let ``Post returns correct result on capacity exceeded`` () =    let imp _ = Failure CapacityExceeded    use sut = new ReservationsController(imp)    let rendition : ReservationRendition = {        Date = "2014-08-09+2:00"        Name = "Mark Seemann"        Email = "[email protected]"        Quantity = 4 }

    let result : IHttpActionResult = sut.Post rendition

    test <@ result :? Results.StatusCodeResult @>    test <@ (result :?> Results.StatusCodeResult).StatusCode =                 HttpStatusCode.Forbidden @>

Page 56: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type ReservationsController(imp) =    inherit ApiController()    member this.Post(rendition : ReservationRendition) =        match imp rendition with        | Failure(ValidationError msg) ->            this.BadRequest msg :> IHttpActionResult        | Failure CapacityExceeded ->            this.StatusCode HttpStatusCode.Forbidden :> IHttpActionResult        | Success () -> this.Ok() :> IHttpActionResult

Page 57: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

let getReservedSeats (date : DateTimeOffset) =    let min = DateTimeOffset(date.Date           , date.Offset)    let max = DateTimeOffset(date.Date.AddDays 1., date.Offset)

    use ctx = new ReservationsContext()    query {        for r in ctx.Reservations do        where (min <= r.Date && r.Date < max)        select r.Quantity }    |> Seq.sum

let saveReservation (reservation : BookingApi.Reservation) =    use ctx = new ReservationsContext()    ctx.Reservations.Add(        Reservation(            Date = reservation.Date,            Name = reservation.Name,            Email = reservation.Email,            Quantity = reservation.Quantity)) |> ignore    ctx.SaveChanges() |> ignore

Page 58: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

type Result<'TSuccess,'TFailure> =     | Success of 'TSuccess    | Failure of 'TFailure

let bind f x =     match x with    | Success s -> f s    | Failure f -> Failure f

let map f x =     match x with    | Success s -> Success(f s)    | Failure f -> Failure f

http://fsharpforfunandprofit.com/posts/recipe-part2

Page 59: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

let imp =     Validate.reservation    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)

ReservationRendition -> Result<Reservation,Error>

ReservationRendition -> Result<unit,Error>

int -> (DateTimeOffset -> int) -> Reservation -> Result<Reservation,Error>

Page 60: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

let imp =     Validate.reservation    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)    >> Rop.map SqlGateway.saveReservation

ReservationRendition -> Result<Reservation,Error>

Reservation -> Result<Reservation,Error>

Reservation -> unit

ReservationRendition -> Result<unit,Error>

Page 61: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

let imp =     Validate.reservation    >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats)    >> Rop.map SqlGateway.saveReservationnew ReservationsController(imp) :> _

Page 62: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.
Page 63: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Functional-First: Few Mocks

Functions: Few interfaces

Function composition: Flexible

Page 64: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

Q.E.D.

Page 65: Look, no Mocks! Functional TDD with F# Mark Seemann  @ploeh.

TDD is not dead