Unit Testing a Controller - How Do I Handle the Connection String?

时间:2016-04-04 18:17:11

标签: asp.net-mvc unit-testing web-config

I can make it work, but I want to know what the best practice is and why. I have a Controller, a Model, and a Repository and now I want to Unit Test the Controller. I am just writing a simple test to ensure that the correct view is being returned.

This is my method in the controller:

    public ActionResult Selections(SelectionsViewModel model)
    {
        for (int i = 0; i < model.Sends.Count; i++)
        {
            Send send = new Send(new SendService(new Database().GetConnectionString()))
            {
                SendID = model.Sends[i].SendID,
                Title = model.Sends[i].Title,
                Subject = model.Sends[i].Subject,
                SentDate = model.Sends[i].SentDate,
                TimesViewed = model.Sends[i].TimesViewed,
                Include = model.Sends[i].Include,
                Exclude = model.Sends[i].Exclude
            };
            send.UpdateIncludeExclude();
        }

        return View(model);
    }

Here is my GetConnectionString() method in the Database class that is being sent via my SendService constructor.

    public string GetConnectionString()
    {
        return System.Configuration.ConfigurationManager.ConnectionStrings["DEVConnectionString"].ToString();
    }

And lastly, my unit test:

    [Test]
    public void TestAssignmentSelections()
    {
        var obj = new AssignmentController();

        var actResult = obj.Selections() as ViewResult;

        NUnit.Framework.Assert.That(actResult.ViewName, Is.EqualTo("Selections"));
    }

Now, my unit test fails, and I get why. My unit test project has no access to the web.config of the project I am testing where my connection string resides.

I've done some research, and apparently just adding a web.config to my unit test project and putting the connection string in there as well will make it work.. but that seems like a hack.

What's the best way to go about this? Is there another way to write my code to accommodate for this?

1 个答案:

答案 0 :(得分:3)

You want to make your controller unit testable ? Don't do this.

new SendService(

With this code,you are hardcoding your concrete service implementation & your data access code implementation. In your unit test, you should not be really accessing the data from your database. Instead you should be providing a mock data access implementation.

Here comes interfaces, you need to create an interface for your SendService.

public interface ISendService
{ 
  void SomeMethod();
}

now your SendService will be a concrete implementation of this interface

public class SendService : ISendService
{
   public void SomeMethod()
   {
     // Do something
   }
}

Now update your controller to have a constructor where we will inject an implementation of ISendService.

public class YourController : Controller
{
   private ISendService sendService;
   public YourController(ISendService sendService)
   {
     this.sendService = sendService;
   }      
   public ActionResult YourActionMethod()
   {
    // use this.sendService.SomeMethod();
   }
}

And you may use some dependency injection frameworks to tell the MVC framework which implementation of the interface to use when the code runs. If you are using MVC6,It has an inbuilt dependency injection provider you can use. So go to your Startup class and in your ConfigureServices method, you can map an interface to a concrete implementation.

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
     services.AddTransient<ISendService, SendService>();
  }
}

If you are in a previous version of MVC, You may consider DI frameworks like Unity,Ninject etc. You can do the same approach for your Data access later / Service layers. ie: Create an interface for data access and inject that to your SendService.

public Interface IDataAccess
{
  string GetName(int id);
}

and an implementation which uses your specific data access code/ORM

public class EFDataAccess : IDataAccess
{
  public string GetName(int id)
  {
     // return a string from db using EF
  }
}

So now your Service class will be

public class SendService : ISendService
{
  private IDataAccess dataAccess;
  public SendService(IDataAccess dataAccess)
  {
    this.dataAccess=dataAccess;
  } 
  // to do : Implement methods of your ISendService interface. 
  // you may use this.dataAccess in those methods as needed.
}

In your unit tests, you can create a mock implementation of your interfaces which returns static data instead of accessing the database.

For example, If you are using Moq mocking framework, you can do this.

var m = new Mock<IDataAccess>();
var m.Setup(s=>s.GetName(It.IsAny<int>())).Returns("Test");
var s = new SendService(m);
var result= s.SomeMethod();