2

I have a static method in a static class that fails under unit testing. The problem is that it requires the value of a public static property from a different static class. The value of the property in the unit test is always null because the source class is not initialized. Both static classes are in the same project. If I execute the method directly, I get the desired results, but I am unable to use a test.

Static Class A:

static class App {

  private static string appDir;

  public static string AppDir => appDir;

  [STAThread]
  static void Main() {

    appDir = AppDomain.CurrentDomain.BaseDirectory;

    DbEng.PutIdbVal("x", "Y");  // Method under test - Works here

  }
}

Static Class B:

public static class DbEng {

  private static SQLiteConnection idbCn = new SQLiteConnection("Data Source=" + App.AppDir + "gcg.idb"); // App.AppDir is valid when not testing, is null when testing.

  public static void PutIdbVal(string key, string value) {

  using (var cmd = new SQLiteCommand("INSERT INTO tKv (Key, Value) VALUES (@key, @value)", idbCn)) {
      cmd.Parameters.Add(new SQLiteParameter("@key", key));
      cmd.Parameters.Add(new SQLiteParameter("@value", value));
      idbCn.Open();
      cmd.ExecuteNonQuery();
      idbCn.Close();
    }
  }
}

Unit Test:

[TestClass]
public class DbEng_Tests {
  [TestMethod]
  public void PutIdbVal_Test() {

    string TestKey = "Test-Key";
    string TestValue = "Test - Value";

    DbEng.PutIdbVal(TestKey, TestValue);

  }
}

Is it possible to force the unit testing code to initialize static class A before calling the method in static class B?

CoolBots
  • 4,770
  • 2
  • 16
  • 30
pro3carp3
  • 807
  • 2
  • 7
  • 17
  • 1
    Are you sure it's because the static class is not initialised? More likely that the AppDomain base directory is null because you are running in a unit test – JimBobBennett May 31 '16 at 01:12
  • Your code has dependency on order of initialization of static variables. Since such order is not defined behavior you observe is perfectly fine (not necessary what you want, but fine nevertheless). – Alexei Levenkov May 31 '16 at 01:18
  • Since you are using MSTest, you can use `[DeploymentItem]` to copy a version of the _gcg.idb_ file for testing. See [MSTest copy file to test run folder](http://stackoverflow.com/questions/649106/mstest-copy-file-to-test-run-folder). Though I would prefer test specific config files... – Andrés Robinet May 31 '16 at 01:19
  • @CoolBots: I have "using Static [namespace].App;" in class B so adding the class prefix is redundant. I didn't include that in the example- so my fault. – pro3carp3 Jun 01 '16 at 13:13
  • @pro3carp3 Ahh, I didn't think about the `using` statement! I figured based on your question that wasn't the problem though, I.just thought something got lost during question posting. Glad it all worked out :) – CoolBots Jun 01 '16 at 15:41

2 Answers2

2

Static classes are initialized upon first access prior to first use of any static member of that class. Unit Test code is no exception to this rule.

To answer your question directly, it is possible to force the unit testing code to initialize static class A before calling the method in static class B - to do so, you just need to access any public static member of class A:

string appDir = App.AppDir;

However, this may not be what's wrong with your code, as you're accessing App.AppDir (if you accepted my edit of your question, which originally just had AppDir) in class B, which should initialize it properly.

The variable appDir of your class A is initialized in static void Main(), which doesn't run on Unit Testing. You should add a static constructor instead:

static App()
{
     appDir = AppDomain.CurrentDomain.BaseDirectory;
}
CoolBots
  • 4,770
  • 2
  • 16
  • 30
  • Note that "upon first access" is putting lightly *misinformation*. Reality is more harsh - [C# spec](https://msdn.microsoft.com/en-us/library/aa645758%28v=vs.71%29.aspx) - "executed at an implementation-dependent time prior to the first use of a static field of that class" (or SO [question](http://stackoverflow.com/questions/1494735/initialization-order-of-static-fields-in-static-class)). – Alexei Levenkov May 31 '16 at 01:30
  • @AlexeiLevenkov Sure, the exact time is not easily determined as per spec, but would you agree that initialization happens *prior to use*? Is that fair to say? – CoolBots May 31 '16 at 01:36
  • Yes, it is perfectly fine... Unfortunately it makes OP's code non-deterministic and hence failure is expected outcome of that code rather than bug (code had to be changed to avoid that uncertainty). – Alexei Levenkov May 31 '16 at 02:08
  • @AlexeiLevenkov adding a static constructor and moving the initialization of `appDir` into it from `static void Main()` should do the trick, as the constructor will run *prior to first use* of `App.AppDir`, would you agree? – CoolBots May 31 '16 at 02:14
  • Plausible. I honestly don't know - this is way to complicated for regular people to know and rely on. I would not write such code in a first place and not let anyone around me to do so. – Alexei Levenkov May 31 '16 at 02:18
  • @AlexeiLevenkov Agreed, overall there's likely a better way; but per SO rules, and in light of not knowing OP's overall situation, I am addressing the original question in my answer rather than suggesting an alternative method. – CoolBots May 31 '16 at 02:21
  • Moving the assignment to the static initializer solved the problem. Thanks to all for your assistance. – pro3carp3 Jun 01 '16 at 13:18
1

Just change your class App to bypass the static field and put the directory in the static property getter. The field appDir most likely isn't set yet, because Main() isn't called before the static field initializer on idbCn is called.

static class App {

  public static string AppDir => AppDomain.CurrentDomain.BaseDirectory;

  [STAThread]
  static void Main() {

    DbEng.PutIdbVal("x", "Y");  // Method under test - Works here

  }
}
Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
  • I accepted CoolBot's answer as it contained the solution and that post was first, however this answer is correct and concise. Thank you. – pro3carp3 Jun 01 '16 at 13:23