0

Currently we are working on a new project which utilizes a class library. I'm developing a feature for the site, working on the basics of a repository which right now has simple CRUD operation methods talking to a database. We have recently decided to start trying to unit-test our code, to further encourage good programming habits.

Right now my Unit Test is failing because it is trying to use a stored procedure on our Live DB - one that does not currently exist on the Live. At first it simply failed (threw an exception) and I spoofed the HttpContext and handed it the server name URL which should trigger connecting to the test DB. However it is connecting to live currently The code which I believe to be causing this is:

public static bool TestMode()
    {
        if (
            HttpContext.Current.Request.ServerVariables["SERVER_NAME"] == "localhost" ||
            HttpContext.Current.Request.ServerVariables["SERVER_NAME"] == "test.org"
            )
            return true;
        else
            return false;
    }

The library is intended to be used in combination with an MVC site, so this type of set up is not uncalled for, and unfortunately it is not my choice on refactoring how we do this.

This being said here's my unit test

[TestClass]
public class CalendarTests {

    public CalendarRepository _repo { get; set; }

    [TestInitialize]
    public void Setup() {
        _repo = new CalendarRepository();

        HttpContext.Current = new HttpContext(
                                new HttpRequest(null, "http://test.org", null),
                                new HttpResponse(null)
                              );
    }

    [TestMethod]
    public void CreateEventTest() {

        int val = _repo.CreateEvent(1,
                                    "test title",
                                    "demo-description",
                                    DateTime.Now,
                                    DateTime.Now.AddDays(1),
                                    true,
                                    1,
                                    1);

        Assert.IsTrue(val > 0);
    }
}

Most of the searches I've done are having this issue because they're testing a controller on an MVC project, this is however slightly different and I'm unsure of where to go from here. I know I need to find a way to mock the context in order to test. Various testing frameworks are suggested all over like NUnit, and also mocking frameworks like NSubstitute. I'm interested in these but unsure if their use is warranted.

Any and all help is appreciated!

EDIT:

What I've tried:

Although I deleted the code I attempted to write something similar to this as well as this

EDIT2:

This is where the connection string comes from

public static string DSHA()
    {
        var db = new Database()
        {
            LiveConnectionString = "sanitized-live",
            TestConnectionString = "sanitized-test"
        };

        return ResolveConnection(db);
    }

This is the code TestMode is called by

private static string ResolveConnection(Database db)
    {
        if (Helpers.TestMode())
            return db.TestConnectionString;
        else
            return db.LiveConnectionString;
    }

So I ask for the connection string, which calls DSHA, resulting in the evaluation from ResolveConnection whose conditional is TestMode ultimately deciding the resulting returned connection string.

EDIT2:

Nkosi's reply has been extremely hlepful, however now I'm trying to ensure that Helpers is getting the default ServerVariables genuinely supplied by the server. I tried to use a constructor

public Helpers(){
  ServerVariable = new ServerVariables();
}

However when I check the Context in a unit test I get a null reference exception, I'm thinking it's because Helpers has static methods iwhtin it, but the Helpers class itself is not static.

[TestMethod]
public void CheckContext(){
    var context = Helpers.ServerVariable.Name;
    Assert.IsTrue(context == "localhost");
}

I'm unsure of proper ways to inject the interface using a container but want to avoid changing the Helpers class behavior as much as possible as there's quite a bit of code written using it. Needing to supply it anything else could mean quite a bit of hunting to ensure it's behavior is changed everywhere.

Community
  • 1
  • 1
wyijx
  • 1
  • 4
  • Testing anything with `HttpContext` is difficult. That is why many have suggested that you abstract the needed functionality behind an interface so that testability can be improved. – Nkosi May 19 '16 at 13:48
  • How would I go about that? I tried to use a wrapper method on it but unsure if I implemented it correctly. – wyijx May 19 '16 at 13:50
  • Show what you've tried and see if the community can point out where you went wrong – Nkosi May 19 '16 at 13:51
  • I added a few links to show what I was talking about – wyijx May 19 '16 at 17:33
  • ok show the code that uses the static `TestMode`. Because as it currently is you will have to do some refactoring. You also mention that you are actually calling a database. is this an integration test or unit test? – Nkosi May 19 '16 at 18:10
  • I suppose this is more of an integration test, sorry for poor terminology. Let me find that code. – wyijx May 19 '16 at 19:47
  • Ok. now that I see how it is being used. Here is my suggestion. Try to limit the use of static classes. they are difficult to test. Next create an abstraction that you can hide the `HttpContext` behind. That way you can swap it out when need during tests. The same advice would apply for anywhere you directly interact with `HttpContext`. – Nkosi May 19 '16 at 20:44
  • I'm guessing that the repo uses the `DSHA` method? – Nkosi May 19 '16 at 20:46
  • Yes, essentially when initializing my SqlConnection I use the static DSHA method to determine my string, which cascades down to the TestMethod and ultimately resolves based on the HttpContext property server_name. I'll try to create an abstraction for HttpContext. – wyijx May 20 '16 at 16:03
  • Cool. Are you using `HttpContext` any where else that may cause an issue during tests? – Nkosi May 20 '16 at 16:06
  • I believe there are two places but both are checking the http.context's session variables to check server_name. – wyijx May 20 '16 at 16:35
  • I'm posting an answer to address your last comment – Nkosi May 20 '16 at 16:40
  • Use a static constructor `private static Helpers(){...}` to set the default value for static `ServerVariable` property. Also, if you are setting a new value in the constructor, you are not really injecting it into the class. – Nkosi May 23 '16 at 15:40
  • What would constitute "injecting" it into the class? I'll try a static constructor right now. – wyijx May 23 '16 at 15:46

1 Answers1

1

From the comments you indicated that you basically need to get the Server name from the HttpContext.

Abstract that functionality behind a service that wraps the HttpContext and just gives you what you need.

something like...

public interface IServerVariable {
    string Name { get; }
}

which you can have an implementation...

public class ServerVariables : IServerVariable {
    public string Name {
        get {
            return HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
        }
    }
}

the IServerVariable can be mocked as needed for testing. So instead of referencing HttpContext directly you can inject your interface to get the data you need.

You may have to refactor your current code to allow for injecting the interface via a container otherwise you will have to do it manually. But other than that this should make your code more testable.

Assuming...

public class Helpers {

    public static IServerVariable ServerVariable {get;set;}

    public static bool TestMode()
    {
        if (ServerVariable != null 
            && (ServerVariable.Name == "localhost" || ServerVariable.Name == "test.org")
            )
            return true;
        else
            return false;
    }

}

a test could look like...

[TestClass]
public class CalendarTests {

    private class FakeServerVariable : IServerVariable {
        public string Name { get { return "test.org"; }
    }

    public CalendarRepository _repo { get; set; }

    [TestInitialize]
    public void Setup() {
        _repo = new CalendarRepository();

        Helpers.ServerVariable = new FakeServerVariable();
    }

    [TestMethod]
    public void CreateEventTest() {

        int val = _repo.CreateEvent(1,
                                    "test title",
                                    "demo-description",
                                    DateTime.Now,
                                    DateTime.Now.AddDays(1),
                                    true,
                                    1,
                                    1);

        Assert.IsTrue(val > 0);
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Nkosi, this has been a ton of help. Now I'm correctly calling the data however I'm a little lost on the proper way to inject the interface for my Helpers class to use. public Helpers(){ ServerVariable = new ServerVarialbes(); } I am unsure how to know that by default Helpers will use the normal httpcontext obtained if not provided one the way you illustrated in the Tests – wyijx May 23 '16 at 15:24
  • Are you still using the static Helper example I gave or did you refactor to something else. – Nkosi May 23 '16 at 15:32
  • In your startup you can create the default class and pass it to the helpers. I wouldn't advise newing one up in the constructor. Given that you didn't want to have to make too many changes to your existing code, that is why I structured my answer they way I did. – Nkosi May 23 '16 at 15:35
  • I'm using your example verbatim and I did succesfully use the test database, but I need to ensure by default it retains it's normal functionality. – wyijx May 23 '16 at 15:44
  • While I'm not a fan of this structure, you can use a static constructor and set the default value there. – Nkosi May 23 '16 at 15:46
  • How would you advise to refactor it? – wyijx May 23 '16 at 15:51
  • Similar to how you did it in the test `Setup()`. there should be an entry point for your application where you do some sort of configuration. You can set the property there. But as I don't know the structure of your project I can't know for sure if that is viable. – Nkosi May 23 '16 at 15:54