### Quick Example: Start Stream, Append Events, and Aggregate Source: https://fisher.jasperfx.net/events Shows how to start a stream with initial events, save changes, and aggregate the stream. The StartStream method returns a StreamAction whose Id is the stream's identity. ```csharp public record InvoiceCreated(decimal Amount, string Customer); public record InvoicePaid(decimal AmountPaid, DateTimeOffset PaidAt); await using var session = store.LightweightSession(); // StartStream hands back a StreamAction; its Id is the stream's identity. var stream = session.Events.StartStream( new InvoiceCreated(100m, "Acme Corp"), new InvoicePaid(100m, DateTimeOffset.UtcNow)); await session.SaveChangesAsync(); var invoice = await session.Events.AggregateStreamAsync(stream.Id); ``` -------------------------------- ### Wait for non-stale data after starting projection daemon Source: https://fisher.jasperfx.net/testing/integration Starts the projection daemon and waits for non-stale data after appending events. Non-stale does not imply a post-commit listener has run; wait on the listener's own signal. ```cs var daemon = await store.BuildProjectionDaemonAsync(); await daemon.StartAllAsync(); // …append events… await daemon.WaitForNonStaleData(TimeSpan.FromSeconds(5)); ``` -------------------------------- ### Configure Stream Identity as Guid or String Source: https://fisher.jasperfx.net/events Sets the stream identity strategy for the event store. The default is AsGuid; AsString is the alternative. ```csharp opts.Events.StreamIdentity = StreamIdentity.AsGuid; // the default opts.Events.StreamIdentity = StreamIdentity.AsString; ``` -------------------------------- ### Isolate stores with a unique table prefix Source: https://fisher.jasperfx.net/testing/integration Sets a unique database schema name using a GUID to isolate logical stores in one file. Note that they still share one write lock; separate files provide concurrency as well as isolation. ```csharp opts.DatabaseSchemaName = $"test_{Guid.NewGuid():n}"; ``` -------------------------------- ### Test for two streams are two stubs Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Unit test verifying that a handler with two streams gets two stubs. Sets each stub's Id to align with the command's IDs, then asserts the appended events on each stream. ```csharp [Fact] public void two_streams_are_two_stubs() { var from = new StubEventStream(new Account { Balance = 100m }) { Id = Guid.NewGuid() }; var to = new StubEventStream(new Account { Balance = 0m }) { Id = Guid.NewGuid() }; TransferHandler.Handle(new Transfer(from.Id, to.Id, 25m), from, to); from.EventsAppended.ShouldBe([new AccountDebited(25m)]); to.EventsAppended.ShouldBe([new AccountDebited(-25m)]); } ``` -------------------------------- ### Throwaway store fixture with DocumentStore Source: https://fisher.jasperfx.net/testing/integration Creates a DocumentStore backed by a unique temporary file, applies schema changes, and cleans up on disposal. Dispose the store before deleting the file to avoid issues with pooled connections. ```csharp public sealed class store_fixture : IAsyncDisposable { private readonly string _path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():n}.db"); public DocumentStore Store { get; private set; } = null!; public async Task InitializeAsync() { Store = DocumentStore.For(opts => { opts.Connection($"Data Source={_path}"); opts.Projections.Snapshot(SnapshotLifecycle.Inline); }); await Store.ApplyAllConfiguredChangesToDatabaseAsync(); } public async ValueTask DisposeAsync() { await Store.DisposeAsync(); // releases this store's pooled connections File.Delete(_path); } } ``` -------------------------------- ### Store, load, and query a document with Fisher Source: https://fisher.jasperfx.net/documents Shows the core Fisher workflow: define a document class, store it in a lightweight session, load it by id, and query with LINQ. Requires a configured store instance and the Fisher session API. ```csharp // Define a document public class User { public Guid Id { get; set; } public string FirstName { get; set; } = ""; public string LastName { get; set; } = ""; public string Email { get; set; } = ""; } // Store a document await using var session = store.LightweightSession(); var user = new User { FirstName = "Jane", LastName = "Doe", Email = "jane@example.com" }; session.Store(user); await session.SaveChangesAsync(); // Load by id var loaded = await session.LoadAsync(user.Id); // Query with LINQ var users = await session.Query() .Where(x => x.LastName == "Doe") .ToListAsync(); ``` -------------------------------- ### Register a document type up front Source: https://fisher.jasperfx.net/testing/integration Registers the Order document type in the schema. This ensures its table is included in the initial migration, avoiding per-type migrations on first read or write. Note that reads against unregistered types still work by provisioning on demand, except in enlisted sessions. ```csharp opts.Schema.For(); ``` -------------------------------- ### Mocking IEventStream (Don't) Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Shows the discouraged approach of mocking IEventStream with Substitute.For. The last line only proves AppendOne was called, not which event or its content, and breaks on refactors using AppendMany. ```csharp // Don't var stream = Substitute.For>(); stream.Aggregate.Returns(new Account { Balance = 100m }); WithdrawHandler.Handle(new Withdraw(Guid.NewGuid(), 30m), stream); stream.Received(1).AppendOne(Arg.Any()); ``` -------------------------------- ### Use the projection scenario harness Source: https://fisher.jasperfx.net/testing/integration Appends events and asserts a document exists with a condition. Teardown clears the event store and document types owned by registered projections, not every table. ```cs await store.Advanced.EventProjectionScenarioAsync(scenario => { scenario.Append(streamId, new OrderPlaced("Acme", 100m)); scenario.Append(streamId, new OrderShipped(DateTimeOffset.UtcNow)); scenario.DocumentShouldExist(streamId, order => Assert.True(order.Shipped)); }); ``` -------------------------------- ### Test: frozen account is refused and appends nothing Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Asserts that a frozen account throws InvalidOperationException and appends no events, highlighting the importance of asserting the empty case. ```csharp [Fact] public void a_frozen_account_is_refused_and_appends_nothing() { var stream = new StubEventStream(new Account { Balance = 100m, IsFrozen = true }); Should.Throw( () => WithdrawHandler.Handle(new Withdraw(stream.Id, 30m), stream)); // The half that is easy to leave out, and the one a mock will not give you for free. stream.EventsAppended.ShouldBeEmpty(); } ``` -------------------------------- ### In-memory database connection string Source: https://fisher.jasperfx.net/testing/integration Configures a Fisher store to use an in-memory SQLite database. The database lives only as long as a connection is held open; Fisher's data source keeps it alive for the store's lifetime. ```csharp opts.Connection("Data Source=test;Mode=Memory;Cache=Shared"); ``` -------------------------------- ### Set identity and versions on StubEventStream Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Sets Id, Key, StartingVersion, and CurrentVersion on a StubEventStream to simulate a version-sensitive handler. The stub populates Id and Key by default, so set whichever the handler reads. ```csharp [Fact] public void the_identity_and_the_versions_are_settable() { var accountId = Guid.NewGuid(); var stream = new StubEventStream(new Account { Balance = 100m }) { Id = accountId, Key = accountId.ToString(), StartingVersion = 7, CurrentVersion = 7 }; WithdrawHandler.Handle(new Withdraw(accountId, 10m), stream); stream.EventsAppended.ShouldHaveSingleItem(); } ``` -------------------------------- ### WithdrawHandler and Withdraw command Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Handles a Withdraw command by loading the aggregate, checking if frozen, appending a debit event, and appending an overdraft event if the balance goes negative. ```csharp public static class WithdrawHandler { public static void Handle(Withdraw command, IEventStream stream) { var account = stream.Aggregate ?? throw new InvalidOperationException("No such account."); if (account.IsFrozen) { throw new InvalidOperationException("The account is frozen."); } stream.AppendOne(new AccountDebited(command.Amount)); if (account.Balance - command.Amount < 0) { stream.AppendOne(new AccountOverdrawn(account.Balance - command.Amount)); } } } public record Withdraw(Guid AccountId, decimal Amount); ``` -------------------------------- ### TransferHandler with two streams Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Defines a Transfer command and a handler that appends AccountDebited events to two IEventStream stubs. The handler appends a positive amount to the 'from' stream and a negative amount to the 'to' stream. ```csharp public record Transfer(Guid FromId, Guid ToId, decimal Amount); public static class TransferHandler { public static void Handle(Transfer command, IEventStream from, IEventStream to) { from.AppendOne(new AccountDebited(command.Amount)); to.AppendOne(new AccountDebited(-command.Amount)); } } ``` -------------------------------- ### Account aggregate and events Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Defines the AccountDebited and AccountOverdrawn events and the Account aggregate with an Apply method that updates the balance. ```csharp public record AccountDebited(decimal Amount); public record AccountOverdrawn(decimal Balance); public class Account { public Guid Id { get; set; } public decimal Balance { get; set; } public bool IsFrozen { get; set; } public void Apply(AccountDebited e) => Balance -= e.Amount; } ``` -------------------------------- ### Test handler with StubEventStream Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Uses StubEventStream to test WithdrawHandler.Handle without a store. Verifies the exact event appended via EventsAppended.ShouldBe. StubEventStream is JasperFx.Events.StubEventStream, not a Fisher type. ```csharp var stream = new StubEventStream(new Account { Balance = 100m }); WithdrawHandler.Handle(new Withdraw(stream.Id, 30m), stream); stream.EventsAppended.ShouldBe([new AccountDebited(30m)]); ``` -------------------------------- ### Assert on EventsAppended vs Events envelopes Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Shows that Events wraps EventsAppended in IEvent envelopes, and that Events.Single().Data returns the raw event body. Use EventsAppended unless you need the envelope, as envelopes carry no save-time data like sequence or version. ```csharp [Fact] public void the_envelopes_carry_the_event_type_naming() { var stream = new StubEventStream(new Account { Balance = 100m }); WithdrawHandler.Handle(new Withdraw(stream.Id, 30m), stream); // Events wraps EventsAppended in IEvent envelopes, for a handler or an assertion that reads // the interface's own member. Assert on EventsAppended unless you need the envelope. stream.Events.Single().Data.ShouldBe(new AccountDebited(30m)); } ``` -------------------------------- ### Query for non-stale data from a session Source: https://fisher.jasperfx.net/testing/integration Queries for non-stale data with a timeout. Non-stale does not imply a post-commit listener has run; wait on the listener's own signal. ```cs var results = await session.Query() .QueryForNonStaleData(TimeSpan.FromSeconds(5)) .ToListAsync(); ``` -------------------------------- ### Reset all data asynchronously Source: https://fisher.jasperfx.net/testing/integration Cleans all data in the store asynchronously. Use this between tests to reset state. ```csharp await store.Advanced.ResetAllDataAsync(); ``` -------------------------------- ### Test: withdrawal within balance debits and nothing else Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Asserts that a withdrawal within the balance appends only the AccountDebited event, using a StubEventStream seeded with a balance of 100m. ```csharp [Fact] public void a_withdrawal_within_the_balance_debits_and_nothing_else() { var stream = new StubEventStream(new Account { Balance = 100m }); WithdrawHandler.Handle(new Withdraw(stream.Id, 30m), stream); // The decision, stated as the events it produced — not as "AppendOne was called". stream.EventsAppended.ShouldBe([new AccountDebited(30m)]); } ``` -------------------------------- ### Clean a single document type asynchronously Source: https://fisher.jasperfx.net/testing/integration Cleans data for a specific document type (Order) asynchronously. Use this to clean only a subset of data between tests. ```csharp await store.Advanced.Clean.CleanAsync(); ``` -------------------------------- ### Test: overdrawing also raises the overdraft event Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Asserts that overdrawing appends both AccountDebited and AccountOverdrawn events, with the overdraft amount being the negative balance. ```csharp [Fact] public void overdrawing_also_raises_the_overdraft_event() { var stream = new StubEventStream(new Account { Balance = 20m }); WithdrawHandler.Handle(new Withdraw(stream.Id, 50m), stream); stream.EventsAppended.ShouldBe([ new AccountDebited(50m), new AccountOverdrawn(-30m) ]); } ``` -------------------------------- ### Test: stream that does not exist yet has a null aggregate Source: https://fisher.jasperfx.net/testing/unit-testing-event-handlers Asserts that a stream with a null aggregate (never written) causes the handler to throw InvalidOperationException, as it requires an existing aggregate. ```csharp [Fact] public void a_stream_that_does_not_exist_yet_has_a_null_aggregate() { var stream = new StubEventStream(null); Should.Throw( () => WithdrawHandler.Handle(new Withdraw(stream.Id, 30m), stream)); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.