internal void BatchIDGeneratorTests(
            BatchIDGenerator gen,
            TestClock clock,
            DateTime now,
            EventIDGenerator res,
            EventID id
            )
        {
            GIVEN["a generator without a last EventID"] = () => {
                clock = new TestClock();
                gen   = BatchIDGenerator.Create(clock: clock);
            };

            GIVEN["a preset time"]          = () => clock.SetUtcNow(now = DateTime.UtcNow);
            WHEN["calling GetBatch"]        = () => res = gen.GetBatch().Result;
            AND["getting the first ID"]     = () => id = res.Next();
            THEN["its event sequence is 1"] = () => id.Sequence.Should().Be(1);
            AND["its batch sequence is 1"]  = () => id.BatchID.Sequence.Should().Be(1);
            AND["its timestamp is correct"] = () => id.BatchID.Timestamp.Should().BeCloseTo(now, precision: 10);

            GIVEN["a new time very close to last"]           = () => clock.SetUtcNow(now.AddTicks(1));
            WHEN["calling GetBatch again"]                   = () => id = gen.GetBatch().Result.Next();
            THEN["the event sequence of first EventID is 1"] = () => id.Sequence.Should().Be(1);
            AND["its batch sequence is 2"]                   = () => id.BatchID.Sequence.Should().Be(2);

            GIVEN["a new time"]      = () => clock.SetUtcNow(now = now.AddMilliseconds(10));
            WHEN["calling GetBatch"] = () => id = gen.GetBatch().Result.Next();
            THEN["the event sequence of first EventID is 1"] = () => id.Sequence.Should().Be(1);
            AND["its batch sequence is 1"]  = () => id.BatchID.Sequence.Should().Be(1);
            AND["its timestamp is correct"] = () => id.BatchID.Timestamp.Should().BeCloseTo(now, precision: 10);

            GIVEN["a new time"] = () => clock.SetUtcNow(now = now.AddMilliseconds(10));
            THEN["calling GetBatch for 511 times takes less than 10ms"] = () => {
                Action action = () => Enumerable.Range(1, 511).ForEach(i => res = gen.GetBatch().Result);
                action.ExecutionTime().Should().BeLessThan(100.Milliseconds());
            };
            AND["the batch sequence of the first EventID is 511 (max)"]         = () => (id = res.Next()).BatchID.Sequence.Should().Be(511);
            Then["calling GetBatch once more", ThrowsA <EventStoreException>()] = () => gen.GetBatch();

            GIVEN["a clock the advances with each call"] = () => clock.SetUtcNow(new PresetTimes(now, now.AddTicks(1), now = now.AddMilliseconds(20)));
            WHEN["calling GetBatch"]        = () => id = gen.GetBatch().Result.Next();
            AND["its batch sequence is 1"]  = () => id.BatchID.Sequence.Should().Be(1);
            AND["its timestamp is correct"] = () => id.BatchID.Timestamp.Should().BeCloseTo(now, precision: 10);
        }
        internal void Save(
            MongoFake db,
            MongoEventStore store,
            IEventStoreTransaction tx,
            PresetIDGenerator generator,
            EventBatch <Customer> s,
            BatchID batch
            )
        {
            GIVEN["a configured store"] = () => store = CreateStore(
                new List <StreamConfiguration> {
                new StreamConfiguration(typeof(Customer), "customer"),
                new StreamConfiguration(typeof(OrderProcessor), "order_processor")
            },
                db        = new MongoFake(),
                generator = new PresetIDGenerator()
                );

            Given["a transaction"] = async() => tx = store.UseTransaction(await db.StartTransactionAsync());

            Then["saving an unregistered stream type", ThrowsA <EventStoreConfigurationException>()] = () =>
                                                                                                       tx.Save(CreateStream <Order>(streamID: Guid.NewGuid()));

            GIVEN["a stream with some events"] = () => s = CreateStream <Customer>(
                streamID: Guid.NewGuid(),
                new Customer.Created(),
                new Customer.Relocated {
                OldAddress = "ADR 1", NewAddress = "ADR 2"
            }
                );

            When["saving the stream"] = () => {
                batch = new BatchID(DateTime.UtcNow);
                generator.Enqueue(batch);
                return(tx.Save(s));
            };

            THEN["an EventID is assigned to each event"] = () => {
                EventIDGenerator gen = new EventIDGenerator(batch);

                s.Events.Select(x => x.ID).Should()
                .AllBeOfType <EventID>().And
                .ContainInOrder(gen.Next(), gen.Next());
            };

            AND["the events are persisted properly"] = () =>
                                                       db.Log.Should().BeExactly(b => b
                                                                                 .Transaction(t => t
                                                                                              .InsertMany("Events", s.Events.ToArray()) // TODO: Better interface on Fake...
                                                                                              .Upsert("customer_Info", s.StreamID, new StreamInfo(s.StreamID))
                                                                                              )
                                                                                 );

            List <RecordedEvent> act = default;

            WHEN["getting the saved stream"]             = () =>
                                                     act = tx.Get(new StreamLocator <Customer>(s.StreamID)).Result.ToList().Result;

            THEN["it contains the original events"] = () =>
                                                      act.Should().BeEquivalentTo(s.Events);
        }