'Create class and auto-initialize dependencies with FakeItEasy

Is it possible to create a class under test with FakeItEasy, where all dependencies that are declared in the constructor are initialized automatically with fakes?

Imagine the class:

public class Inserting
{
    public Inserting(
        ITransactionService transactionService,
        ISharedData sharedData)
    {
        TransactionService = transactionService;
        SharedData = sharedData;
    }

    public ITransactionService TransactionService { get; }

    public ISharedData SharedData { get; }

    public void Enter()
    {
        TransactionService.StartTransaction();
    }
}

Then I am creating all fake-objects in the test setup and construct my class under test with those fakes:

public class InsertingTest
{
    private Inserting _inserting;
    private ISharedData _fakeSharedData;
    private ITransactionService _fakeTransactionService;        

    [SetUp]
    public void SetUp()
    {
        _fakeTransactionService = A.Fake<ITransactionService>();
        _fakeSharedData = A.Fake<ISharedData>();

        _inserting = new Inserting(_fakeTransactionService, _fakeSharedData);
    }

    [Test]
    public void TestEnter()
    {
        // Arrange

        // Act
        _inserting.Enter();

        // Assert
        A.CallTo(() => _fakeTransactionService.StartTransaction().MustHaveHappened();
    }
}

But I saw in the Java-world, that when using Mockito and Dagger 2, you can do something like this:

public class PhoneDialer {
    private Activity activity;
    private PhoneCallListener phoneCallListener;

    @Inject
    public PhoneDialer(Activity activity, PhoneCallListener phoneCallListener) {
        this.activity = activity;
        this.phoneCallListener = phoneCallListener;
    }
}

public class PhoneDialerTest {
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    PhoneCallListener phoneCallListener;

    @Mock
    Activity activity;

    @InjectMocks
    PhoneDialer dialer;

    @Test
    public void test_dialer() throws Exception {
        // Arrange

        // Act
        dialer.callNumber("abc");

        // Assert
        Mockito.verify(phoneCallListener, times(1)).startCall();
    }
}

and the mocked classes are initialized automatically with fakes. Is there an equivalent procedure or function in C# with FakeItEasy?



Solution 1:[1]

I think you want something like Automatically inject fakes in test fixture with FakeItEasy. You use [Fake] to mark fakes to inject and [UnderTest] to mark the production type to test.

We really should put this into the documentation.

Alternatively,

Solution 2:[2]

I saw 'Automatically inject fakes in text fixture with FakeItEasy' and my initial reaction was surprise that it differed from my preconception, mainly because it needs 'intrusive' changes that attribute the test code... but perhaps that is an overreaction.

The FakeAttribute and UnderTestAttribute do force what is potentially a good structural constraint on your test (and system) design...

[FWLIW, before googling this, I had imagined the following:

    containerBuilder.RegisterAsFakeCallingBaseType<SystemUnderTest>();

You can do something like this with Autofac's registration sources.

using Autofac;
using Autofac.Core;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Lifetime;
using Autofac.Core.Registration;
using FakeItEasy;
using Xunit;

    public interface IDependOnSomething { }
    public class IImplementThat : IDependOnSomething { }

    public class CanIResolveIt
    {
        public CanIResolveIt(IDependOnSomething it)
        {
        }
    }

    public class FakeRegistrationSourceTest
    {
        [Fact]
        public void BasicTest()
        {
            var container = new ContainerBuilder();
            container.RegisterTypes<IImplementThat>().As<IDependOnSomething>();
            container.RegisterSource(new FakeRegistrationSource<CanIResolveIt>());
            var c = container.Build();
            var theFake = c.Resolve<CanIResolveIt>();
            Assert.NotNull(theFake);
        }
    }

    public class FakeRegistrationSource<T> : IRegistrationSource
        where T : class
    {
        public bool IsAdapterForIndividualComponents => false;

        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            var swt = service as IServiceWithType;
            if (swt == null || !typeof(T).IsAssignableFrom(swt.ServiceType)) // TODO: is this the right way around?
            {
                return Enumerable.Empty<IComponentRegistration>();
            }

            var registration = new ComponentRegistration(
              Guid.NewGuid(),
              
              new DelegateActivator(swt.ServiceType, (context, @params) =>
              {
                  List<object> v = new List<object>();
                  foreach (ParameterInfo p in typeof(T).GetConstructors().Single().GetParameters())
                  {
                      v.Add(context.Resolve(p.ParameterType));
                  }

                  return A.Fake<T>(that => that.CallsBaseMethods().WithArgumentsForConstructor(v));
              }),
              new CurrentScopeLifetime(),
              InstanceSharing.None,
              InstanceOwnership.OwnedByLifetimeScope,
              new[] { service },
              new Dictionary<string, object>());

            return new IComponentRegistration[] { registration };
        }
    }

Main advantage of this approach is that it knows how to instantiate fake objects subclassing classes with constructor parameters, and inheriting their default behavior, when they have a single constructor (choosing intelligently from multiple constructors would be an obvious challenge that I'm not going to tackle...)

An obvious drawback is explicit registration every time you want something faked. AutoFake and so on offer ways to overcome that with faking of just about everything by default, which might well be what you want... and you can override it if not.]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2