Huh? Where do I put my service so that my Controller and Service aren't impossible to test?
Am I correct to think that I have to create my controller by passing it an instance of my context AND my Service to make it testable?
For example: new Controller(mycontext,myservice)
I'm thinking this is the way I need to change my code, but I don't want to if I don't have to. Since for MVC3 to work out of the box it requires controller constructors to be parameterless, I think this means I'm going to have to go down the path of IoC. Otherwise the code in my Wizard action saves to a real DBContext even during testing.
namespace mvc3test.Controllers
{
public class WizardController : Controller
{
private DR405DBContext db;
public WizardController(DR405DBContext dbContext)
{
db = dbContext;
}
public WizardController()
{
db = new DR405DBContext();
}
public ActionResult Index()
{
var model = new WizardViewModel();
model.Initialize();
return View(model);
}
[HttpPost]
public ActionResult Index([Deserialize] WizardViewModel wizard)
{
//wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
//Always save.
var obj = new dr405();
//wire up to domain model;
foreach (var s in wizard.Steps)
{
Mapper.Map(s,obj,s.GetType(), typeof(dr405));
}
using (var service = new DR405Service())
开发者_JAVA百科 {
//Do something with a service here.
service.Save(db, obj);
}
if (!string.IsNullOrEmpty(Request.QueryString["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request.QueryString["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
return View("Review", wizard);
}
}
else if (!string.IsNullOrEmpty(Request.QueryString["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
public ActionResult Review(int id)
{
var service = new DR405Service();
var dr405 = service.GetDR405ById(db, id);
var wizard = new WizardViewModel();
if (dr405 != null)
{
wizard.Initialize();
foreach (var s in wizard.Steps)
{
Mapper.Map(dr405, s, typeof(dr405), s.GetType());
}
}
return View(wizard);
}
public ActionResult Transmit()
{
return View();
}
[HttpPost]
public String Upload(HttpPostedFileBase FileData)
{
var saveLocation = Path.Combine(Server.MapPath("\\"), "returns\\" + DR405Profile.CurrentUser.TaxPayerID);
System.IO.Directory.CreateDirectory(saveLocation);
FileData.SaveAs(Path.Combine(saveLocation, FileData.FileName));
ViewBag.Message = String.Format("File name: {0}, {1}Kb Uploaded Successfully.", FileData.FileName, (int)FileData.ContentLength / 1024);
return ViewBag.Message;
}
}
}
Am I correct to think that I have to create my controller by passing it an instance of my context AND my Service to make it testable?
Kind of. That's only half of the work you need to do. The second half is to weaken the coupling between your layers by using abstractions. Your service layer needs to implement an interface which you would inject into the constructor of your controller enforcing the contract between those two and explicitly stating that the controller needs a service layer obeying this contract:
public WizardController(IMyService service)
{
this._service = service;
}
Now in your unit test go ahead and mock it using one of the multiple mocking frameworks out there (Rhino Mocks, NSubstitute, Moq, NMock, ...).
You can use setter injection instead of constructor injection on the Controller.
public class WizardController : Controller
{
public void setDBContext( DR405DBContext db) {
this.db = db;
}
}
or
You can get the database using a Service Locator and add a setter to that.
public class DBServiceLocator
{
private static DR405DBContext db = new DR405DBContext();
public static DR405DBContext locate() {
return db;
}
public static setContext(DR405DBContext db) {
DBServiceLocator.db = db;
}
}
In the setup() part of your unit test, use setters to 'inject' your mock/stub database.
Also, using an interface rather than DR405DBContext will make mocking easier.
精彩评论