0

Using version 7.3.0 of FakeItEasy. In following code I am getting message that call to method GetById is not configured, yet I am configuring it. What I am doing wrong? There is no overload for the GetById method.

var fakeConnection = A.Fake<IEventStoreConnection>();
var fakeResolver = A.Fake<IEventResolver>();
var logger = A.Fake<ILogger<DummyAggregateRepository>>();

var repository = new DummyAggregateRepository(fakeConnection, fakeResolver, logger);

var fakeRepository = A.Fake<DummyAggregateRepository>(
    o =>
    {
        o.Wrapping(repository);
        o.Strict();
    });

var aggregateId = Guid.NewGuid();

A.CallTo(() => fakeRepository.GetById(aggregateId, A<CancellationToken>._))
    .CallsWrappedMethod();

var fixture = new AutoFixture.Fixture();

var events1 = fixture.CreateMany<DummyAggregate.EventOne>(10).Cast<Event>();
var events2 = fixture.CreateMany<DummyAggregate.EventTwo>(10).Cast<Event>();

var events = events1.Union(events2).ToList();

A.CallTo(
        () => fakeRepository.GetEvents(
            "dummyAggregate-" + aggregateId.ToString("N"),
            aggregateId.ToString(),
            A<CancellationToken>._))
    .Returns(events);

var aggregate = await fakeRepository.GetById(aggregateId, default);

GetById implementation is virtual method

public virtual async Task<TAggregate> GetById(Guid aggregateId, CancellationToken ct)
        {
            ct.ThrowIfCancellationRequested();

            var streamName = this.GetStreamName(aggregateId);

            using var scope = EventStoreCommon.CreateScope(Tracer.Instance, nameof(this.GetById), streamName);

            var events = await this.GetEvents(streamName, aggregateId.ToString(), ct);

            if (events.Any() == false)
            {
                throw new AggregateNotFoundException(aggregateId, typeof(TAggregate));
            }

            var aggregate = new TAggregate();

            foreach (var @event in events)
            {
                aggregate.ApplyEvent(@event);
            }

            return aggregate;
        }

Error reported

FakeItEasy.ExpectationException: Call to unconfigured method of strict fake: MyCompany.EventStore.Test.AggregateRepositoryTests.DummyAggregateRepository...

FakeItEasy.ExpectationException
Call to unconfigured method of strict fake: MyCompany.EventStore.Test.AggregateRepositoryTests.DummyAggregateRepository.GetById(aggregateId: d8d0445d-7f82-4636-82fc-2e8f14be7f3d, ct: System.Threading.CancellationToken).
   at FakeItEasy.Core.StrictFakeRule.Apply(IInterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\StrictFakeRule.cs:line 53
   at FakeItEasy.Core.FakeManager.ApplyBestRule(IInterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeManager.cs:line 276
   at FakeItEasy.Core.FakeManager.FakeItEasy.Core.IFakeCallProcessor.Process(InterceptedFakeObjectCall fakeObjectCall) in C:\projects\fakeiteasy\src\FakeItEasy\Core\FakeManager.cs:line 178
   at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.ProxyInterceptor.Intercept(IInvocation invocation) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 187
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.DummyAggregateRepositoryProxy.GetById(Guid aggregateId, CancellationToken ct)
   at MyCompany.EventStore.Test.AggregateRepositoryTests.GetByIdTests.When_Stream_Exists_Should_Create_Instance_Of_Aggregate_With_Applied_Events() in C:\github\MyCompany_2\libraries\eventstore\test\MyCompany.EventStore.Test\AggregateRepositoryTests\GetByIdTests.cs:line 131
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
   at Xunit.Sdk.AsyncTestSyncContext.<>c__DisplayClass7_0.<Post>b__1(Object _) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\AsyncTestSyncContext.cs:line 75
epitka
  • 17,275
  • 20
  • 88
  • 141

1 Answers1

0

Updated after getting new code and stack trace. Original answer left below

I think it's a bug.

Boring explanation:

Strict and Wrapping each add rules to the front of the faked object's rules list. The rules apply to all incoming calls. The first rule found that matches a call is triggered. So in your configuration, Wrapping is added making the Fake attempt to forward all calls to the wrapped object. Then Strict, so now the Fake will reject all calls.

You'd then expect

A.CallTo(() => fakeRepository.GetById(aggregateId, A<CancellationToken>._))
    .CallsWrappedMethod();

to add a rule that would cause an incoming matching GetById call to be forwarded to the wrapped method, but the implementation of CallsWrappedMethod

appears to be missing a this.AddRuleIfNeeded() call around line 127. (You can see that CallsBaseMethod just above has this call.)

I've verified that no rule is added by the CallsWrappedMethod configuration. Likely has been broken since implemented in FakeItEasy 6.0.0 (issue #1717).

I created issue #1870 which is now fixed and released as part of FakeItEasy 7.3.1.


Original response, which was wrong, but did ask for more info:

Likely because a call was made to an unconfigured method on a strict faked repository.

We can't see DummyAggregateRepository, so there's going to be some speculation here. Based on the lack of errors earlier, I assume that GetById is virtual or abstract, so your call should be passed to the wrapped instance.

Nothing jumps out at me as I read your question, but this sort of thing usually pops up when

  1. a call is made to a different fake object than expected, or
  2. a call is made to a different method than expected, usually because there's an overload and the wrong method was configured
  3. a call is made to the expected fake object method, but with unanticipated arguments

If you edit your question, supplying

  1. the FakeItEasy version you're using
  2. the definition of DummyAggregateRepository, and
  3. the entire exception that's returned, including stack traces

we may be able to help better.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • Thank @Blair Conrad for looking into issue, and detailed explanation. I just realized that test will not work anyway, because second call to .GetEvents virtual method gets called from .GetById, so there is no way to fake this. – epitka Mar 08 '22 at 09:01
  • Yeah, I noticed the `.GetEvents` in there. You may be better off using `CallsBaseMethod`, which at least keeps you executing on the Fake, but, I expect you'd still be adding an `Invokes` to your configuration of `GetById`. It might get a little messy. – Blair Conrad Mar 08 '22 at 11:26
  • 1
    Actually, being unable to do this made me rethink responsibilities of the class towards better design. – epitka Mar 11 '22 at 06:13