Edument.CQRS/BDDTest.cs
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;
namespace Edument.CQRS
{
/// <summary>
/// Provides infrastructure for a set of tests on a given command handler
/// and aggregate.
/// </summary>
/// <typeparam name="TCommandHandler"></typeparam>
/// <typeparam name="TAggregate"></typeparam>
public class BDDTest<TCommandHandler, TAggregate, TReadModel>
//where TCommandHandler : new()
where TAggregate : Aggregate, new()
where TReadModel : class
{
private TCommandHandler _sut;
private TReadModel _readModel;
protected void SystemUnderTest(TCommandHandler commandHandler)
{
SystemUnderTest(commandHandler, null);
}
protected void SystemUnderTest(TCommandHandler commandHandler, TReadModel readModel)
{
_sut = commandHandler;
_readModel = readModel;
}
protected void Test(IEnumerable given, Func<TAggregate, object> when, Action<object> then)
{
then(when(ApplyEvents(new TAggregate(), given)));
}
protected IEnumerable Given(params object[] events)
{
return events;
}
protected Func<TAggregate, object> When<TCommand>(TCommand command)
{
return agg =>
{
try
{
return DispatchCommand(_ => agg, command).Cast<object>().ToArray();
}
catch (Exception e)
{
return e;
}
};
}
protected Action<object> Then(params object[] expectedEvents)
{
return got =>
{
var gotEvents = got as object[];
if (gotEvents != null)
{
if (gotEvents.Length == expectedEvents.Length)
{
for (var i = 0; i < gotEvents.Length; i++)
{
if (gotEvents[i].GetType() == expectedEvents[i].GetType())
Assert.AreEqual(Serialize(expectedEvents[i]), Serialize(gotEvents[i]));
else
Assert.Fail(string.Format("Incorrect event in results; expected a {0} but got a {1}",
expectedEvents[i].GetType().Name, gotEvents[i].GetType().Name));
}
}
else if (gotEvents.Length < expectedEvents.Length)
{
Assert.Fail(string.Format("Expected event(s) missing: {0}",
expectedEvents.Select(e => e.GetType().Name)
.Except(gotEvents.Select(e => e.GetType().Name))));
}
else
{
var expectedNames = expectedEvents.Select(e => e.GetType().Name);
var gotNames = gotEvents.Select(e => e.GetType().Name);
var unexpectedEvents = gotNames.Except(expectedNames);
Assert.Fail(string.Format("Unexpected event(s) emitted: \n\t{0}", string.Join("\n\t", unexpectedEvents)));
}
}
else if (got is CommandHandlerNotDefiendException)
{
Assert.Fail((got as Exception).Message);
}
else
{
Assert.Fail("Expected events, but got exception {0}", got.GetType().Name);
}
};
}
protected Action<object> ThenFailWith<TException>()
{
return got =>
{
if (got is TException)
{ /*Assert.Pass("Got correct exception type");*/ }
else if (got is CommandHandlerNotDefiendException)
Assert.Fail((got as Exception).Message);
else if (got is Exception)
Assert.Fail(string.Format(
"Expected exception {0}, but got exception {1}",
typeof(TException).Name, got.GetType().Name));
else
Assert.Fail(string.Format(
"Expected exception {0}, but got event result",
typeof(TException).Name));
};
}
private IEnumerable DispatchCommand<TCommand>(Func<Guid, TAggregate> al, TCommand c)
{
var handler =_sut as IHandleCommand<TCommand, TAggregate>;
if (handler == null)
throw new CommandHandlerNotDefiendException(string.Format(
"Command handler {0} does not yet handle command {1}",
_sut.GetType().Name, c.GetType().Name));
return handler.Handle(al, c);
}
private TAggregate ApplyEvents(TAggregate agg, IEnumerable events)
{
agg.ApplyEvents(events);
return agg;
}
private string Serialize(object obj)
{
var ser = new XmlSerializer(obj.GetType());
var ms = new MemoryStream();
ser.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
return new StreamReader(ms).ReadToEnd();
}
private class CommandHandlerNotDefiendException : Exception
{
public CommandHandlerNotDefiendException(string msg) : base(msg) { }
}
}
}