-
-
Save MarkNijhof/507143 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD | |
{ | |
[Specification] | |
public abstract class BaseTestFixture | |
{ | |
protected Exception CaughtException; | |
protected virtual void Given() { } | |
protected abstract void When(); | |
protected virtual void Finally() { } | |
[Given] | |
public void Setup() | |
{ | |
CaughtException = new ThereWasNoExceptionButOneWasExpectedException(); | |
Given(); | |
try | |
{ | |
When(); | |
} | |
catch (Exception exception) | |
{ | |
CaughtException = exception; | |
} | |
finally | |
{ | |
Finally(); | |
} | |
} | |
} | |
public class GivenAttribute : SetUpAttribute { } | |
public class ThenAttribute : TestAttribute { } | |
public class SpecificationAttribute : TestFixtureAttribute { } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD | |
{ | |
public class Very_adding_a_number_to_the_subject_under_test : BaseTestFixture | |
{ | |
private int SubjectUnderTest; | |
protected override void Given() | |
{ | |
SubjectUnderTest = 2; | |
} | |
protected override void When() | |
{ | |
SubjectUnderTest += 2; | |
} | |
[Then] | |
public void Then_the_new_value_of_the_subject_under_test_will_be_4() | |
{ | |
SubjectUnderTest.WillBe(4); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD | |
{ | |
[Specification] | |
public abstract class BaseTestFixture<TSubjectUnderTest> | |
{ | |
private Dictionary<Type, object> mocks; | |
protected Dictionary<Type, object> DoNotMock; | |
protected TSubjectUnderTest SubjectUnderTest; | |
protected Exception CaughtException; | |
protected virtual void SetupDependencies() { } | |
protected virtual void Given() { } | |
protected abstract void When(); | |
protected virtual void Finally() { } | |
[Given] | |
public void Setup() | |
{ | |
mocks = new Dictionary<Type, object>(); | |
DoNotMock = new Dictionary<Type, object>(); | |
CaughtException = new ThereWasNoExceptionButOneWasExpectedException(); | |
BuildMocks(); | |
SetupDependencies(); | |
SubjectUnderTest = BuildSubjectUnderTest(); | |
Given(); | |
try | |
{ | |
When(); | |
} | |
catch (Exception exception) | |
{ | |
CaughtException = exception; | |
} | |
finally | |
{ | |
Finally(); | |
} | |
} | |
public Mock<TType> OnDependency<TType>() where TType : class | |
{ | |
return (Mock<TType>)mocks[typeof(TType)]; | |
} | |
private TSubjectUnderTest BuildSubjectUnderTest() | |
{ | |
var constructorInfo = typeof(TSubjectUnderTest).GetConstructors().First(); | |
var parameters = new List<object>(); | |
foreach (var mock in mocks) | |
{ | |
object theObject; | |
if (!DoNotMock.TryGetValue(mock.Key, out theObject)) | |
theObject = ((Mock) mock.Value).Object; | |
parameters.Add(theObject); | |
} | |
return (TSubjectUnderTest)constructorInfo.Invoke(parameters.ToArray()); | |
} | |
private void BuildMocks() | |
{ | |
var constructorInfo = typeof(TSubjectUnderTest).GetConstructors().First(); | |
foreach (var parameter in constructorInfo.GetParameters()) | |
{ | |
mocks.Add(parameter.ParameterType, CreateMock(parameter.ParameterType)); | |
} | |
} | |
private static object CreateMock(Type type) | |
{ | |
var constructorInfo = typeof(Mock<>).MakeGenericType(type).GetConstructors().First(); | |
return constructorInfo.Invoke(new object[] { }); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD.Scenarios.Receiving_money_transfer | |
{ | |
public class When_receiving_a_money_transfer : BaseTestFixture<MoneyReceiveService> | |
{ | |
protected override void SetupDependencies() | |
{ | |
OnDependency<IReportingRepository>() | |
.Setup(x => x.GetByExample<AccountReport>(It.IsAny<object>())) | |
.Returns(new List<AccountReport> { new AccountReport(Guid.NewGuid(), Guid.NewGuid(), "AccountName", "target account number") }); | |
} | |
protected override void When() | |
{ | |
SubjectUnderTest.Receive(new MoneyTransfer("source account number", "target account number", 123.45M)); | |
} | |
[Then] | |
public void Then_the_newly_created_account_will_be_saved() | |
{ | |
OnDependency<IBus>().Verify(x => x.Publish(It.IsAny<ReceiveMoneyTransferCommand>())); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD.Scenarios.Adding_a_new_client | |
{ | |
public class When_in_the_GUI_the_phone_number_of_the_new_client_is_saved : PresenterTestFixture<ClientDetailsPresenter> | |
{ | |
private object CreateClientCommand; | |
protected override void SetupDependencies() | |
{ | |
OnDependency<IPopupPresenter>() | |
.Setup(x => x.CatchPossibleException(It.IsAny<Action>())) | |
.Callback<Action>(x => x()); | |
OnDependency<IBus>() | |
.Setup(x => x.Publish(It.IsAny<object>())) | |
.Callback<object>(x => CreateClientCommand = x); | |
} | |
protected override void Given() | |
{ | |
Presenter.SetClient(null); | |
Presenter.Display(); | |
On<IClientDetailsView>().ValueFor(x => x.ClientName).IsSetTo("New Client Name"); | |
On<IClientDetailsView>().FireEvent(x => x.OnFormElementGotChanged += null); | |
On<IClientDetailsView>().FireEvent(x => x.OnSaveNewClientName += null); | |
On<IClientDetailsView>().ValueFor(x => x.Street).IsSetTo("Street"); | |
On<IClientDetailsView>().ValueFor(x => x.StreetNumber).IsSetTo("123"); | |
On<IClientDetailsView>().ValueFor(x => x.PostalCode).IsSetTo("5000"); | |
On<IClientDetailsView>().ValueFor(x => x.City).IsSetTo("Bergen"); | |
On<IClientDetailsView>().FireEvent(x => x.OnFormElementGotChanged += null); | |
On<IClientDetailsView>().FireEvent(x => x.OnSaveNewAddress += null); | |
On<IClientDetailsView>().ValueFor(x => x.PhoneNumber).IsSetTo("1234567890"); | |
On<IClientDetailsView>().FireEvent(x => x.OnFormElementGotChanged += null); | |
} | |
protected override void When() | |
{ | |
On<IClientDetailsView>().FireEvent(x => x.OnSaveNewPhoneNumber += null); | |
} | |
[Then] | |
public void Then_the_save_button_will_be_disabled() | |
{ | |
On<IClientDetailsView>().VerifyThat.Method(x => x.DisableSaveButton()).WasCalled(); | |
} | |
[Then] | |
public void Then_a_create_client_command_with_all_collected_information_will_be_published() | |
{ | |
On<IBus>().VerifyThat.Method(x => x.Publish(It.IsAny<CreateClientCommand>())).WasCalled(); | |
CreateClientCommand.As<CreateClientCommand>().ClientName.WillBe("New Client Name"); | |
CreateClientCommand.As<CreateClientCommand>().Street.WillBe("Street"); | |
CreateClientCommand.As<CreateClientCommand>().StreetNumber.WillBe("123"); | |
CreateClientCommand.As<CreateClientCommand>().PostalCode.WillBe("5000"); | |
CreateClientCommand.As<CreateClientCommand>().City.WillBe("Bergen"); | |
CreateClientCommand.As<CreateClientCommand>().PhoneNumber.WillBe("1234567890"); | |
} | |
[Then] | |
public void Then_overview_panel_will_be_shown() | |
{ | |
On<IClientDetailsView>().VerifyThat.Method(x => x.Close()).WasCalled(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD | |
{ | |
public class MockDsl<TType> where TType : class | |
{ | |
private readonly IDictionary<Type, object> _mocks; | |
public MockDsl(IDictionary<Type, object> mocks) | |
{ | |
_mocks = mocks; | |
} | |
public ValueSetter<TType, TProperty> ValueFor<TProperty>(Expression<Func<TType, TProperty>> selector) | |
{ | |
return new ValueSetter<TType, TProperty>(_mocks, selector); | |
} | |
public void FireEvent(Action<TType> fieldSelector) | |
{ | |
if (!_mocks.ContainsKey(typeof(TType))) | |
throw new Exception(string.Format("The requested dependency '{0}' is not specified in the constructor", typeof(TType).FullName)); | |
var mock = (Mock<TType>)_mocks[typeof(TType)]; | |
mock.Raise(fieldSelector); | |
} | |
public Verifier<TType> VerifyThat { get { return new Verifier<TType>(_mocks); } } | |
} | |
public class Verifier<TType> where TType : class | |
{ | |
private readonly IDictionary<Type, object> _mocks; | |
public Verifier(IDictionary<Type, object> mocks) | |
{ | |
_mocks = mocks; | |
} | |
public void ValueIsSetFor(Action<TType> selector) | |
{ | |
if (!_mocks.ContainsKey(typeof(TType))) | |
throw new Exception(string.Format("The requested dependency '{0}' is not specified in the constructor", typeof(TType).FullName)); | |
var mock = (Mock<TType>)_mocks[typeof(TType)]; | |
mock.VerifySet(selector); | |
} | |
public MethodVerifier<TType> Method(Expression<Action<TType>> selector) | |
{ | |
return new MethodVerifier<TType>(_mocks, selector); | |
} | |
} | |
public class MethodVerifier<TType> where TType : class | |
{ | |
private readonly IDictionary<Type, object> _mocks; | |
private readonly Expression<Action<TType>> _fieldSelector; | |
public MethodVerifier(IDictionary<Type, object> mocks, Expression<Action<TType>> fieldSelector) | |
{ | |
_mocks = mocks; | |
_fieldSelector = fieldSelector; | |
} | |
public void WasCalled() | |
{ | |
if (!_mocks.ContainsKey(typeof(TType))) | |
throw new Exception(string.Format("The requested dependency '{0}' is not specified in the constructor", typeof(TType).FullName)); | |
var mock = (Mock<TType>)_mocks[typeof(TType)]; | |
mock.Verify(_fieldSelector); | |
} | |
} | |
public class ValueSetter<TType, TProperty> where TType : class | |
{ | |
private readonly IDictionary<Type, object> _mocks; | |
private readonly Expression<Func<TType, TProperty>> _fieldSelector; | |
public ValueSetter(IDictionary<Type, object> mocks, Expression<Func<TType, TProperty>> fieldSelector) | |
{ | |
_mocks = mocks; | |
_fieldSelector = fieldSelector; | |
} | |
public void IsSetTo(TProperty value) | |
{ | |
if (!_mocks.ContainsKey(typeof(TType))) | |
throw new Exception(string.Format("The requested dependency '{0}' is not specified in the constructor", typeof(TType).FullName)); | |
var mock = (Mock<TType>)_mocks[typeof(TType)]; | |
mock.SetupGet(_fieldSelector).Returns(value); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD | |
{ | |
[Specification] | |
public abstract class CommandTestFixture<TCommand, TCommandHandler, TAggregateRoot> | |
where TCommand : class, ICommand | |
where TCommandHandler : class, ICommandHandler<TCommand> | |
where TAggregateRoot : class, IOrginator, IEventProvider<IDomainEvent>, new() | |
{ | |
private IDictionary<Type, object> mocks; | |
protected TAggregateRoot AggregateRoot; | |
protected ICommandHandler<TCommand> CommandHandler; | |
protected Exception CaughtException; | |
protected IEnumerable<IDomainEvent> PublishedEvents; | |
protected virtual void SetupDependencies() { } | |
protected virtual IEnumerable<IDomainEvent> Given() | |
{ | |
return new List<IDomainEvent>(); | |
} | |
protected virtual void Finally() { } | |
protected abstract TCommand When(); | |
[Given] | |
public void Setup() | |
{ | |
mocks = new Dictionary<Type, object>(); | |
CaughtException = new ThereWasNoExceptionButOneWasExpectedException(); | |
AggregateRoot = new TAggregateRoot(); | |
AggregateRoot.LoadFromHistory(Given()); | |
CommandHandler = BuildCommandHandler(); | |
SetupDependencies(); | |
try | |
{ | |
CommandHandler.Execute(When()); | |
PublishedEvents = AggregateRoot.GetChanges(); | |
} | |
catch (Exception exception) | |
{ | |
CaughtException = exception; | |
} | |
finally | |
{ | |
Finally(); | |
} | |
} | |
public Mock<TType> OnDependency<TType>() where TType : class | |
{ | |
return (Mock<TType>)mocks[typeof(TType)]; | |
} | |
private ICommandHandler<TCommand> BuildCommandHandler() | |
{ | |
var constructorInfo = typeof(TCommandHandler).GetConstructors().First(); | |
foreach (var parameter in constructorInfo.GetParameters()) | |
{ | |
if (parameter.ParameterType == typeof(IDomainRepository<IDomainEvent>)) | |
{ | |
var repositoryMock = new Mock<IDomainRepository<IDomainEvent>>(); | |
repositoryMock.Setup(x => x.GetById<TAggregateRoot>(It.IsAny<Guid>())).Returns(AggregateRoot); | |
repositoryMock.Setup(x => x.Add(It.IsAny<TAggregateRoot>())).Callback<TAggregateRoot>(x => AggregateRoot = x); | |
mocks.Add(parameter.ParameterType, repositoryMock); | |
continue; | |
} | |
mocks.Add(parameter.ParameterType, CreateMock(parameter.ParameterType)); | |
} | |
return (ICommandHandler<TCommand>)constructorInfo.Invoke(mocks.Values.Select(x => ((Mock) x).Object).ToArray()); | |
} | |
private static object CreateMock(Type type) | |
{ | |
var constructorInfo = typeof (Mock<>).MakeGenericType(type).GetConstructors().First(); | |
return constructorInfo.Invoke(new object[]{}); | |
} | |
} | |
public class ThereWasNoExceptionButOneWasExpectedException : Exception {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Test.Fohjin.DDD.Scenarios.Withdrawing_cash | |
{ | |
public class When_withdrawing_cash : CommandTestFixture<WithdrawlCashCommand, WithdrawlCashCommandHandler, ActiveAccount> | |
{ | |
protected override IEnumerable<IDomainEvent> Given() | |
{ | |
yield return new AccountOpenedEvent(Guid.NewGuid(), Guid.NewGuid(), "AccountName", "1234567890"); | |
yield return new CashDepositedEvent(20, 20); | |
} | |
protected override WithdrawlCashCommand When() | |
{ | |
return new WithdrawlCashCommand(Guid.NewGuid(), 5); | |
} | |
[Then] | |
public void Then_a_cash_withdrawn_event_will_be_published() | |
{ | |
PublishedEvents.Last().WillBeOfType<CashWithdrawnEvent>(); | |
} | |
[Then] | |
public void Then_the_published_event_will_contain_the_amount_and_new_account_balance() | |
{ | |
PublishedEvents.Last<CashWithdrawnEvent>().Balance.WillBe(15); | |
PublishedEvents.Last<CashWithdrawnEvent>().Amount.WillBe(5); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment