84

I have a base class for my tests which is composed in the following way:

[TestClass]
public abstract class MyBaseTest
{
   protected static string myField = "";

   [ClassInitialize]
   public static void ClassInitialize(TestContext context)
   {
       // static field initialization
       myField = "new value";
   }
}

Now I am trying to create a new test that inherits from the base, with the following signature:

[TestClass]
public class MyTest : MyBaseTest
{
   [TestMethod]
   public void BaseMethod_ShouldHave_FieldInitialized()
   {
       Assert.IsTrue(myField == "new value");
   }
}

The ClassInitialize is never called by the child tests ... What is the real and correct way of using test initialization with inheritance on MsTest?

Aaron Palmer
  • 8,912
  • 9
  • 48
  • 77
Raffaeu
  • 6,694
  • 13
  • 68
  • 110

5 Answers5

51

Unfortunately you cannot achieve this that way because the ClassInitializeAttribute Class cannot be inherited.

An inherited attribute can be used by the sub-classes of the classes that use it. Since the ClassInitializeAttribute cannot not be inherited, when the MyTest class is initialized the ClassInitialize method from the MyBaseTest class cannot be called.

Try to solve it with another way. A less efficient way is to define again the ClassInitialize method in MyTest and just call the base method instead of duplicating the code.

Onorio Catenacci
  • 14,928
  • 14
  • 81
  • 132
chaliasos
  • 9,659
  • 7
  • 50
  • 87
  • 34
    I tried your approach and it works, but honestly Microsoft should fix this because NUnit doesn't have this behavior. – Raffaeu Apr 12 '13 at 08:25
  • When you say “the ClassInitializeAttribute Class cannot be inherited”, are you referring to the class being `sealed`?—that should not affect how the attribute appears when applied to an inherited method… – binki May 05 '15 at 13:54
  • Since it doesn’t set `AttributeUsageAttribute.Inherited` to `false`, it will be inherited… – binki May 05 '15 at 13:54
  • 2
    Oh, I just noticed the methods are `static` which means they don’t participate in normal inheritance. Ahh… – binki May 05 '15 at 14:00
  • 1
    You can upvote this feature in uservoice [here](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2311366-support-classinitialize-in-base-class) – Iman Mahmoudinasab May 23 '15 at 12:32
  • 2
    FYI - [ClassInitialize/ClassCleanup inheritance is currently under development for 2.0.0](https://github.com/microsoft/testfx/issues/143). – NightOwl888 Aug 24 '19 at 18:35
  • If you are using VS 2019 and newer, [Krombir's answer](https://stackoverflow.com/a/74714900/1843468) is the right way to go and should become accepted answer as of now. – LWChris Mar 28 '23 at 08:43
10

A potential workaround is to define a new class with AssemblyInitializeAttribute instead. It has a different scope, obviously, but for me it meets my needs (cross-cutting concerns, which just so happen to require exactly the same settings for every test class and test method.)

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyTests
{
  [TestClass]
  public sealed class TestAssemblyInitialize
  {
    [AssemblyInitialize]
    public static void Initialize(TestContext context)
    {
      ...
    }
  }
}
Dave Sexton
  • 2,562
  • 1
  • 17
  • 26
  • This is what I ended up doing as well, however I am curious; why is your base-test class `sealed`? Won't that prevent you from inheriting that class and doing exactly what you have set out to do? – Niko Jul 06 '16 at 21:47
  • I'm not using it for inheritance. All of my real test classes derive from a different base class. This class is purely for the `AssemblyInitialize` method. It's just a design decision that I made to keep things separated. – Dave Sexton Jul 07 '16 at 23:33
9

There is a parameter for the ClassInitialize and ClassCleanup attributes:

[ClassInitialize(InheritanceBehavior.BeforeEachDerivedClass)]
public static void ClassInitialize(TestContext context)
{
   // gets called once for each class derived from this class
   // on initialization
}

[ClassCleanup(InheritanceBehavior.BeforeEachDerivedClass)]
public static void Cleanup()
{
   // gets called once for each class derived from this class
   // on cleanup
}

which will actually do what you want.

Krombir
  • 113
  • 1
  • 5
  • I suppose this was not available back then, but since VS 2019, this is the correct solution and should be marked as answer. – LWChris Mar 28 '23 at 08:41
1

Use a static constructor on a base class? It's executed only once, by design, and it doesn't have the weird limitation on inheritance, like the ClassInitializeAttribute.

Mladen B.
  • 2,784
  • 2
  • 23
  • 34
  • 1
    This is not a replacement of `[ClassInitialize]`: It will be called without context when the complete assembly is loaded. So it isn't better than `[AssemblyInitialize]`! – Marcel Jul 24 '19 at 08:48
0

UPDATE: Added lock to avoid multi-threading issues...

We know that a new instance of the class is constructed for every [TestMethod] in the class as it gets run. The parameter-less constructor of the base class will be called each time this happens. Couldn't you simply create a static variable in the base class and test it when constructor runs?

This helps you to not forget to put the initialization code in the sub-class.

Not sure if there's any drawback to this approach...

Like so:

public class TestBase
{
    private static bool _isInitialized = false;
    private object _locker = new object();

    public TestBase()
    {
        lock (_locker) 
        {
          if (!_isInitialized)
          {
            TestClassInitialize();
            _isInitialized = true;
          }
        }
    }

    public void TestClassInitialize()
    {
        // Do one-time init stuff
    }
}
public class SalesOrderTotals_Test : TestBase
{
    [TestMethod]
    public void TotalsCalulateWhenThereIsNoSalesTax()
    {
    }
    [TestMethod]
    public void TotalsCalulateWhenThereIsSalesTax()
    {
    }
}
C.List
  • 657
  • 8
  • 16
  • 2
    You don't get the TestContext with this solution. That's a bummer. – Andy V Feb 06 '15 at 16:38
  • Also, you don't have CleanUp – bubi Sep 24 '16 at 09:52
  • And you usually don't need either of those. This is a fine solution for cases when it fits, which is often. – BWhite Nov 02 '18 at 22:41
  • A serious flaw here is thread-safety (or the lack of). Since all the tests will be running in parallel, and they'll all call the constructor at approx. the same time, most of them will enter the TestClassInitialize() method until one of those finally exits that method and sets the _isInitialized to true. – Mladen B. Jun 17 '19 at 15:00
  • static members are entirely specific to the declaring class; subclasses do not get separate copies. – Alex from Jitbit Mar 08 '20 at 10:34