forked from Akkatecture/Akkatecture.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpath---docs-testing-aggregates-11488ad95282483104b4.js
2 lines (2 loc) · 23.8 KB
/
path---docs-testing-aggregates-11488ad95282483104b4.js
1
2
webpackJsonp([0xf3660d563b1a],{426:function(e,t){e.exports={data:{allPostTitles:{edges:[{node:{frontmatter:{title:"Getting Started",lesson:1,category:"akkatecture",chapter:1,type:"docs"},fields:{slug:"/getting-started"}}},{node:{frontmatter:{title:"Primitives",lesson:1,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/primitives"}}},{node:{frontmatter:{title:"Walkthrough Introduction",lesson:1,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/walkthrough-introduction"}}},{node:{frontmatter:{title:"Sagas",lesson:1,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/sagas"}}},{node:{frontmatter:{title:"Tips and Tricks",lesson:1,category:"akkatecture",chapter:5,type:"docs"},fields:{slug:"/tips-and-tricks"}}},{node:{frontmatter:{title:"Aggregates",lesson:2,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/aggregates"}}},{node:{frontmatter:{title:"Your First Aggregate",lesson:2,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-aggregate"}}},{node:{frontmatter:{title:"Snapshotting",lesson:2,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/snapshotting"}}},{node:{frontmatter:{title:"Articles",lesson:2,category:"akkatecture",chapter:5,type:"docs"},fields:{slug:"/articles"}}},{node:{frontmatter:{title:"Events",lesson:3,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/events"}}},{node:{frontmatter:{title:"Your First Commands",lesson:3,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-commands"}}},{node:{frontmatter:{title:"Clustering",lesson:3,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/clustering"}}},{node:{frontmatter:{title:"Videos",lesson:3,category:"akkatecture",chapter:5,type:"docs"},fields:{slug:"/videos"}}},{node:{frontmatter:{title:"Commands",lesson:4,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/commands"}}},{node:{frontmatter:{title:"Your First Events",lesson:4,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-events"}}},{node:{frontmatter:{title:"Production Readiness",lesson:4,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/production-readiness"}}},{node:{frontmatter:{title:"Specifications",lesson:5,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/specifications"}}},{node:{frontmatter:{title:"Your First Specifications",lesson:5,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-specifications"}}},{node:{frontmatter:{title:"Event Upgrading",lesson:5,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/event-upgrading"}}},{node:{frontmatter:{title:"Subscribers",lesson:6,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/subscribers"}}},{node:{frontmatter:{title:"Your First Aggregate Test",lesson:6,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-aggregate-test"}}},{node:{frontmatter:{title:"Testing Aggregates",lesson:6,category:"akkatecture",chapter:4,type:"docs"},fields:{slug:"/testing-aggregates"}}},{node:{frontmatter:{title:"Scheduled Jobs",lesson:7,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/scheduled-jobs"}}},{node:{frontmatter:{title:"Your First Aggregate Saga",lesson:7,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-aggregate-saga"}}},{node:{frontmatter:{title:"Akka",lesson:8,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/akka"}}},{node:{frontmatter:{title:"Your First Subscribers",lesson:8,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-subscribers"}}},{node:{frontmatter:{title:"Configuration",lesson:9,category:"akkatecture",chapter:2,type:"docs"},fields:{slug:"/configuration"}}},{node:{frontmatter:{title:"Your First Projections",lesson:9,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/your-first-projections"}}},{node:{frontmatter:{title:"Walkthrough Ending",lesson:10,category:"akkatecture",chapter:3,type:"docs"},fields:{slug:"/walkthrough-ending"}}}]},postBySlug:{html:'<p>One of the biggest benefits of message based systems is that they enable the possibility to express system behaviours purely in terms of message inputs and message outputs. In the context of domain driven design this means that all domain activity can be expressed in terms of commands and events. If we focus to aggregate roots, an important abstraction in domain driven design, we could formulate message based systemstests as expressed in a behaviour driven development style as follows:</p>\n<div class="gatsby-highlight">\n <pre class="language-text"><code class="language-text">1 - For AggregateRoot A\n2 - Given some Precondition\n2 - When Command C on A\n3 - A Should Emit Event E</code></pre>\n </div>\n<p>or a failure case</p>\n<div class="gatsby-highlight">\n <pre class="language-text"><code class="language-text">1 - For AggregateRoot A\n2 - Given some Precondition\n2 - When Command C on A\n3 - A Reply with FailureResult</code></pre>\n </div>\n<p>With this kind of testing that is based on events and commands, you can have clear meaning to the domain expert or business owner. Not only does this mean that tests are expressed in terms of events and cmmands. These tests have a clear functional meaning, it also means that they hardly depend on any implementation details of where your events are stored or how events get published.</p>\n<p>Akkatecture has a companion test package, called <a href="https://www.nuget.org/packages/Akkatecture.TestFixture/">Akkatecture.TestFixture</a> that enables test driven developers to write fluent tests against aggregate roots. Initially, only the support for testing aggregate roots is available, in the future there will be support for testing aggregate sagas.</p>\n<div class="gatsby-highlight">\n <pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserAggregateTests</span> <span class="token punctuation">:</span> <span class="token class-name">TestKit</span>\n<span class="token punctuation">{</span>\n <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">RegisteringUser_UsingRegisterCommand_ShouldEmitRegisteredEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span>\n <span class="token punctuation">{</span>\n <span class="token keyword">var</span> userId <span class="token operator">=</span> UserId<span class="token punctuation">.</span>New<span class="token punctuation">;</span>\n\n <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FixtureFor</span><span class="token punctuation"><</span><span class="token class-name">User</span><span class="token punctuation">,</span> <span class="token class-name">UserId</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span>\n <span class="token punctuation">.</span><span class="token function">GivenNothing</span><span class="token punctuation">(</span><span class="token punctuation">)</span>\n <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RegisterUserCommand</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">)</span>\n <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ThenExpect</span><span class="token punctuation"><</span><span class="token class-name">UserRegisteredEvent</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">></span> x<span class="token punctuation">.</span>RegisteredAt <span class="token operator"><=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span>\n<span class="token punctuation">}</span></code></pre>\n </div>\n<blockquote>\n<p>The given when then test fixture defines three stages: configuration, execution and validation. Each of these stages is represented by a different interface: <a href="https://github.com/Lutando/Akkatecture/blob/master/src/Akkatecture.TestFixture/Aggregates/IFixtureArranger.cs"><code class="language-text">IFixtureArranger<,></code></a>, <a href="https://github.com/Lutando/Akkatecture/blob/master/src/Akkatecture.TestFixture/Aggregates/IFixtureExecutor.cs"><code class="language-text">IFixtureExecutor<,></code></a>, and <a href="https://github.com/Lutando/Akkatecture/blob/master/src/Akkatecture.TestFixture/Aggregates/IFixtureAsserter.cs"><code class="language-text">IFixtureAsserter<,></code></a> respectively. The static <code class="language-text">FixtureFor<,>()</code> extension method extends the akka.net\'s <code class="language-text">TestKitBase</code> which allows it to work for any .net testing framework that you choose. The <code class="language-text">FixtureFor<,>()</code> method is the entry point for writing your tests.</p>\n</blockquote>\n<p>The unit of testing here is the aggregate root, the <code class="language-text">this.FixtureFor<,></code> is meant to test one aggregate root only. Which means that all commands, events, and snapshots that eminate to and from this unit under test is all for the same aggregate root instance. In other words, all of those message types are bound to the same <code class="language-text">aggregateId</code>.</p>\n<h1 id="configuration-stage"><a href="#configuration-stage" aria-hidden="true" class="anchor"><svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Configuration Stage</h1>\n<p>The configuration stage of the aggregate fixture is there to tell the fixture under one preconditions the tests should be run. Typically these preconditions play no role during the assertion phase of your test scenario, but they are important in hydrating your aggregate with behaviours the are required for the test to work as planned. In the configuration stage, you may configure the fixture in one of four ways:</p>\n<ol>\n<li>Configure with no initialization using <code class="language-text">GivenNothing()</code></li>\n<li>Configure with journalled events using <code class="language-text">Given(IAggregateEvent<,>[] events)</code></li>\n<li>Configure with stored snapshot using <code class="language-text">Given(IAggregateSnapshot<,> snapshot)</code></li>\n<li>Configure with commands using <code class="language-text">Given(ICommand<,>[] commands)</code></li>\n</ol>\n<p>Preconfiguring your tests with <code class="language-text">GivenNothing()</code> tells the fixture that you essentially want your aggregate to be initialized with nothing as if it were <code class="language-text">New</code>. If you want to seed your aggregates journal with events, you can do that by using the <code class="language-text">Given(IAggregateEvent<,>[] events)</code> method. This method seeds the default (inmemory) journal to append the events in order that they are given in this method. The <code class="language-text">Given(IAggregateSnapshot<,> snapshot)</code> initializes the default (inmemory) snapshot journal with that given snapshot. This might be a convienient way to hydrate aggregates that are large in your tests, however the reccomendation is not shy away from using snapshots in your testing because snapshots are an aggregate derivative of your event model, the event model should always take preference. And then finally you can use <code class="language-text">Given(ICommand<,>[] commands)</code> for an added level of flexibility.</p>\n<blockquote>\n<p>The event journal and snapshot stored used will typically be the inmemory version as is the default, which is fine for unit tests.</p>\n</blockquote>\n<h1 id="execution-stage"><a href="#execution-stage" aria-hidden="true" class="anchor"><svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Execution Stage</h1>\n<p>The execution stage is the staged is the stage that illistrates the part where the domain is invoked. It is the part of the test that describes what interaction the test is trying to observe. Typically after the execution stage, a validation stage follows that should observe the outputs. A good rule of thumb is to mainly observe the outputs of your aggregrate root that arises as a result of an execution. The execution stage can be executed in one of two ways, the most important of which is:</p>\n<ol>\n<li>Execute by sending command(s) using <code class="language-text">When(ICommand<TAggregate, TIdentity>[] commands)</code></li>\n<li>When youre in a validation stage and want to go back to an execution stage, use <code class="language-text">AndWhen(ICommand<TAggregate, TIdentity>[] commands)</code>\nThe second <code class="language-text">AndWhen(...)</code> execution stage method gives you the ability to chain commands in a fluent way and / or to go from a validation stage into an execution stage again.</li>\n</ol>\n<blockquote>\n<p>Even though there is a similar command sending step in the configuration stage, one should not worry about asserting the outputs of these configuration stage commands, the main concern of any unit test is to observe what happens after a certain action is invoked given a precondition.</p>\n</blockquote>\n<h1 id="validation-stage"><a href="#validation-stage" aria-hidden="true" class="anchor"><svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Validation Stage</h1>\n<p>The validation stage tests the output of your aggregate root after an execution stage has been executed.</p>\n<ol>\n<li>Validate the emitted domain event by virtue of a predicate using <code class="language-text">ThenExpect<TAggregateEvent>(Predicate<TAggregateEvent> aggregateEventPredicate)</code></li>\n<li>Validate the emitted aggregate event portion of the domain event by virtue of a predicate using <code class="language-text">ThenExpect<TAggregateEvent>(Predicate<IDomainEvent> domainEventPredicate)</code></li>\n<li>Validate the <code class="language-text">Reply(...)</code> message of an aggregate root by using <code class="language-text">ThenExpectReply<TReply>(Predicate<TReply> aggregateReply)</code></li>\n</ol>\n<h1 id="testing-chained-behaviours"><a href="#testing-chained-behaviours" aria-hidden="true" class="anchor"><svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing Chained Behaviours</h1>\n<p>There might come times when you need to test out behavioural flows which are the result of multiple independant commands. To enable this you need to be able to go from the validation stage into the execution stage seemlessly. Lets say that we have a business rule that says, once a user is registered, if that user validates their email, they only can then add 2 factor authentication to their account. If we wanted to test this domain rule we could set up a fixture like this:</p>\n<div class="gatsby-highlight">\n <pre class="language-csharp"><code class="language-csharp"><span class="token comment">//example 1</span>\n<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">RegisteredUser_CanAdd2FAuthAfterValidatingEmail_ShouldEmit2FAuthAddedEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span>\n<span class="token punctuation">{</span>\n <span class="token keyword">var</span> userId <span class="token operator">=</span> UserId<span class="token punctuation">.</span>New<span class="token punctuation">;</span>\n\n <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FixtureFor</span><span class="token punctuation"><</span><span class="token class-name">User</span><span class="token punctuation">,</span> <span class="token class-name">UserId</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span> <span class="token comment">//configure</span>\n <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">UserRegisteredEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//initialize</span>\n <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ValidateEmailCommand</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> email<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//invoke</span>\n <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ThenExpect</span><span class="token punctuation"><</span><span class="token class-name">EmailValidatedEvent</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//test</span>\n <span class="token punctuation">.</span><span class="token function">AndWhen</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Add2FAuthenticationCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//invoke again</span>\n <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ThenExpect</span><span class="token punctuation"><</span>2FAuthenticationAddedEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//test again</span>\n<span class="token punctuation">}</span></code></pre>\n </div>\n<p>This fixture can also be tested the following way:</p>\n<div class="gatsby-highlight">\n <pre class="language-csharp"><code class="language-csharp"><span class="token comment">//example 2</span>\n<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">RegisteredUser_CanAdd2FAuthAfterValidatingEmail_ShouldEmit2FAuthAddedEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span>\n<span class="token punctuation">{</span>\n <span class="token keyword">var</span> userId <span class="token operator">=</span> UserId<span class="token punctuation">.</span>New<span class="token punctuation">;</span>\n\n <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FixtureFor</span><span class="token punctuation"><</span><span class="token class-name">User</span><span class="token punctuation">,</span> <span class="token class-name">UserId</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span> <span class="token comment">//configure</span>\n <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">UserRegisteredEvent</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">EmailValidatedEvent</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> email<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//initialize</span>\n <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Add2FAuthenticationCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//invoke</span>\n <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ThenExpect</span><span class="token punctuation"><</span>2FAuthenticationAddedEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//test</span>\n<span class="token punctuation">}</span></code></pre>\n </div>\n<p>The difference between the first example and the second example is that the first example also tests the business rules when it comes to validating emails. The second example does not test email validation business rules, because in its initialize stage, it is given that the email has been validated by the virtue of it already being journalled in the fixtures event journal. In general when designing your tests, business rules should be tested that are based on the execution stage and not the init/configuration stage.</p>\n<blockquote>\n<p>For some examples of how some of these tests may look like, check out the tests in the <a href="https://github.com/Lutando/Akkatecture/blob/master/test/Akkatecture.Tests/UnitTests/Aggregates/AggregateTestsWithFixtures.cs">test project</a>. In the future support for testing aggregate sagas will also be supported.</p>\n</blockquote>',timeToRead:6,excerpt:"One of the biggest benefits of message based systems is that they enable the possibility to express system behaviours purely in terms of…",frontmatter:{title:"Testing Aggregates",cover:"https://unsplash.it/400/300/?random?BoldMage",date:"01/07/2018",category:"akkatecture",tags:["advanced-concepts","akkatecture","csharp","dotnet"]},fields:{slug:"/testing-aggregates"}}},pathContext:{slug:"/testing-aggregates",category:"akkatecture"}}}});
//# sourceMappingURL=path---docs-testing-aggregates-11488ad95282483104b4.js.map