Per definition: "This attribute is to identify methods that are called once prior to executing any of the tests in a fixture. It may appear on methods of a TestFixture or a SetUpFixture." (https://github.com/nunit/docs/wiki/OneTimeSetUp-Attribute)
My solution contains two assemblies: one for my code, and one for my tests.
My code assembly contains a class containing a static property that can only be set once: it throws an exception if someone tries to set it a second time.
My test assembly contains a OneTimeSetUp method that sets that property. That method should be called only once for all the tests of my assembly. This is not the case:
the OneTimeSetUp method is called before each test.
I reproduced this with the latest version of NCrunch (v3.10). Both the built-in TestExplorer of Visual Studio and Resharper behave correctly by calling the method only once.
I don't know how to post the source code, so in the meantime, here it is:
Code project:
Foo.cs
Code:
using System;
using System.Diagnostics;
using System.Threading;
namespace RealCodeAssembly
{
public class Foo
{
public static bool WasCalled { get; set; }
public static string Initializer { get; set; }
public Foo()
{
var processId = Process.GetCurrentProcess().Id;
if (WasCalled)
{
throw new Exception($"OneTimeSetUp has been already called in the same process {processId} (thread {Thread.CurrentThread.ManagedThreadId})\r\n" + Environment.StackTrace + "\r\nWas:\r\n" + Initializer);
}
WasCalled = true;
Initializer = $"ProcessId = {processId} (thread {Thread.CurrentThread.ManagedThreadId})\r\n" + Environment.StackTrace;
}
}
}
(I added some debug code)
Test project
AssemblyLevelClass.cs
Code:
using NUnit.Framework;
using RealCodeAssembly;
[SetUpFixture]
public class AssemblyLevelClass
{
public Foo Foo { get; set; }
[OneTimeSetUp]
public void OneTimeSetUp()
{
Foo = new Foo();
}
}
(Please note that the class above isn't in a namespace).
and the test class:
Code:
using System.Collections.Generic;
using System.Threading;
using NUnit.Framework;
namespace TestNinjectOneTimeSetup
{
[TestFixture]
public class Class1
{
public static IEnumerable<int> DataSource()
{
for (int i = 0; i < 1000; ++i)
{
yield return i;
}
}
[TestCaseSource(nameof(DataSource))]
public void FirstTest(int value)
{
Thread.Sleep(10);
Assert.That(value == -1, Is.False);
}
}
}
Please note that without the Thread.Sleep, NCrunch behaves correctly. I had to add it to reproduce the bug simply. In my real-life application, there's no Thread.Sleep in the code, but the initialization part is quite big (plenty of Ninject bindings etc.)
I have logs like this one:
OneTimeSetUp: System.Exception : OneTimeSetUp has been already called in the same process 21288 (thread 11)
à System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
à System.Environment.get_StackTrace()
à RealCodeAssembly.Foo..ctor() dans f:\dev\TestNinjectOneTimeSetup\RealCodeAssembly\Foo.cs:ligne 17
à AssemblyLevelClass.OneTimeSetUp() dans f:\dev\TestNinjectOneTimeSetup\TestNinjectOneTimeSetup\AssemblyLevelClass.cs:ligne 12
à System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
à System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
à System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
à NUnit.Framework.Internal.Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args)
à NUnit.Framework.Internal.Commands.SetUpTearDownItem.RunSetUp(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.OneTimeSetUpCommand.<>c__DisplayClass0_0.<.ctor>b__0(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.BeforeTestCommand.Execute(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.BeforeTestCommand.Execute(TestExecutionContext context)
à NUnit.Framework.Internal.Execution.CompositeWorkItem.PerformOneTimeSetUp()
à NUnit.Framework.Internal.Execution.CompositeWorkItem.PerformWork()
à NUnit.Framework.Internal.Execution.WorkItem.RunOnCurrentThread()
à NUnit.Framework.Internal.Execution.WorkItem.Execute()
à NUnit.Framework.Internal.Execution.TestWorker.TestWorkerThreadProc()
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()
Was:
ProcessId = 21288 (thread 13)
à System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
à System.Environment.get_StackTrace()
à RealCodeAssembly.Foo..ctor() dans f:\dev\TestNinjectOneTimeSetup\RealCodeAssembly\Foo.cs:ligne 20
à AssemblyLevelClass.OneTimeSetUp() dans f:\dev\TestNinjectOneTimeSetup\TestNinjectOneTimeSetup\AssemblyLevelClass.cs:ligne 12
à System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
à System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
à System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
à NUnit.Framework.Internal.Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args)
à NUnit.Framework.Internal.Commands.SetUpTearDownItem.RunSetUp(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.OneTimeSetUpCommand.<>c__DisplayClass0_0.<.ctor>b__0(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.BeforeTestCommand.Execute(TestExecutionContext context)
à NUnit.Framework.Internal.Commands.BeforeTestCommand.Execute(TestExecutionContext context)
à NUnit.Framework.Internal.Execution.CompositeWorkItem.PerformOneTimeSetUp()
à NUnit.Framework.Internal.Execution.CompositeWorkItem.PerformWork()
à NUnit.Framework.Internal.Execution.WorkItem.RunOnCurrentThread()
à NUnit.Framework.Internal.Execution.WorkItem.Execute()
à NUnit.Framework.Internal.Execution.TestWorker.TestWorkerThreadProc()
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()
The OneTimeSetUp method is called twice in the same process, for the same assembly, in different threads (run sequentially).