Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FakeItEasy sometimes fails to create a fake when tests are run in parallel

Unit tests that use FakeItEasy randomly fail when trying to fake a simple intefrace. It occurs in different tests occasionally and is not stable.

Here is a sample interface I need to fake:

public interface IJobSuiteFilterApplier
{
     JobSuiteDto FilterJobSuites(JobSuiteDto jobSuiteDto, JobSuiteFilter jobSuiteFilter);
}

Here is piece of code that creates the fake and fails sometimes:

var jobSuiteFilterApplier = A.Fake<IJobSuiteFilterApplier>(x => x.Strict());

Here is the exception details:

FakeItEasy.Core.FakeCreationException: 
  Failed to create fake of type "QS.TestShell.Server.ExecutionPlanner.Queries.IExecutionPlannerQueryService".

  Below is a list of reasons for failure per attempted constructor:
    No constructor arguments failed:
      No usable default constructor was found on the type QS.TestShell.Server.ExecutionPlanner.Queries.IExecutionPlannerQueryService.
      An exception was caught during this call. Its message was:
      Collection was modified; enumeration operation may not execute.


    at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithResolvedConstructors(Type typeOfFake, String reasonForFailureOfUnspecifiedConstructor, IEnumerable`1 resolvedConstructors)
   at FakeItEasy.Creation.FakeObjectCreator.TryCreateFakeWithDummyArgumentsForConstructor(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, String failReasonForDefaultConstructor, Boolean throwOnFailure)
   at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, Boolean throwOnFailure)
   at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, FakeOptions options)
   at FakeItEasy.Creation.DefaultFakeCreatorFacade.CreateFake[T](Action`1 options)
   at FakeItEasy.A.Fake[T](Action`1 options)

When I add the following, tests pass, but do it looks strange that I need to add it to all the fake creation:

var jobSuiteFilterApplier = A.Fake<IJobSuiteFilterApplier>(x => x.Strict().Synchronized());



public class CallSynchronizer : IInterceptionListener
{
    private static readonly object SynchronizationLock = new object();

    public void OnBeforeCallIntercepted(IFakeObjectCall interceptedCall)
    {
        Monitor.Enter(SynchronizationLock);
    }

    public void OnAfterCallIntercepted(ICompletedFakeObjectCall interceptedCall, IFakeObjectCallRule ruleThatWasApplied)
    {
        Monitor.Exit(SynchronizationLock);
    }
}

public static class MyPersonalFakeExtensions
{
    public static IFakeOptionsBuilder<T> Synchronized<T>(this IFakeOptionsBuilder<T> builder)
    {
        return builder.OnFakeCreated(fake => Fake.GetFakeManager(fake).AddInterceptionListener(new CallSynchronizer()));

    }
}

Update: I am running the tests using ReSharper test runner on developer machine and using mstext.exe on build server. The concurrency settings allow for multiple tests to be run at once.

like image 773
Boris Modylevsky Avatar asked Oct 22 '14 07:10

Boris Modylevsky


1 Answers

Update: FakeItEasy 2.0.0 has drastically improved support for tests run in parallel. Try it.

as mike z mentioned: FakeItEasy currently doesn't support multi-threaded tests. This is because not all internals are thread-safe, and it's not easy to make it completely thread-safe. There's an open issue, number 60, for supporting multi-threaded test execution.

For now the solution you provided is the only way to achieve this, as originally explained here http://hmemcpy.com/2012/12/running-multithreaded-unit-tests-with-fakeiteasy/.

There is no way to globally add an interception listener to all fakes, however you can use the FakeConfigurator<T> class to automate this behaviour per type, so you could opt to include, for each faked type, a class such as

public class SomeTypeSynchronousConfigurator : FakeConfigurator<ISomeType>
{
    public override void ConfigureFake(ISomeType fakeObject)
    {
        Fake.GetFakeManager(fakeObject)
                 .AddInterceptionListener(new CallSynchronizer());
    }
}

FakeItEasy will discover the class and every new fake of (in this case) ISomeType will have the synchronizer applied—you can make the fake just like var fake = A.Fake<ISomeType>();.

like image 199
philippdolder Avatar answered Nov 08 '22 06:11

philippdolder