我开发了使用帮助器类的API,以获取每个端点函数的数据库上下文。现在,我正在尝试为每个端点编写单元测试,并且想在单元测试项目中使用内存数据库。
我遇到的问题是,为了调用API函数,我必须在API控制器类中添加一个构造函数。这将允许我将内存数据库的dbContext传递给控制器函数以供使用。但是,由于添加了构造器,因此在尝试命中端点时出现以下错误:
"exceptionMessage": "Unable to resolve service for type 'AppointmentAPI.Appt_Models.ApptSystemContext' while attempting to activate 'AppointmentAPI.Controllers.apptController'."
controller.cs
public class apptController : Controller
{
private readonly ApptSystemContext DbContext;
public apptController(ApptSystemContext dbContext)
{
if (dbContext == null)
{
// Use helper to get dbContext. Should be used when hitting endpoint
DbContext = ConnectionHelper.getApptConnection();
}
else
{
// dbContext should be in-memory db. This context should only be used for unit tests
DbContext = dbContext;
}
}
#region assingAppt
/*
* assignAppt()
*
* Assigns newly created appointment to slot
* based on slotId
*
*/
[Authorize]
[HttpPost]
[Route("/appt/assignAppt")]
public string assignAppt([FromBody] dynamic apptData)
{
int id = apptData.SlotId;
string json = apptData.ApptJson;
DateTime timeStamp = DateTime.Now;
using (DbContext)
{
var slot = DbContext.AppointmentSlots.Single(s => s.SlotId == id);
// make sure there isn't already an appointment booked in appt slot
if (slot.Timestamp == null)
{
slot.ApptJson = json;
slot.Timestamp = timeStamp;
DbContext.SaveChanges();
return "Task Executed\n";
}
else
{
return "There is already an appointment booked for this slot.\n" +
"If this slot needs changing try updating it instead of assigning it.";
}
}
}
}
异常表明您尚未在Startup.cs中注册DBContext(如上所述)。我还建议您将私有只读属性的名称更改为DbContext以外的其他名称(这是类名称,可能会引起混淆)使用类似这样的内容:
private readonly ApptSystemContext _context;
此外,您的方法应该更改。
首先,在注册DBContext时将设置连接字符串。只是让依赖注入为您解决这个问题。您的控制器应如下所示:
public apptController(ApptSystemContext dbContext)
{
_context = dbContext;
}
如果您在启动中注册,则dbContext不会为null。
接下来,单元测试是一个棘手的概念,但是一旦您编写了单元测试,您就会开始对它有所了解。
[您已经说过要使用SQL In Memory db进行单元测试,这是一种很好的方法(请注意,SQL In Mem存在一些限制,就像没有FK约束一样)。接下来,假设您要测试Controller,因此,由于必须传递DBContext才能实例化Controller,因此可以创建一个配置为使用内存数据库的新DBContext实例。
例如
public void ApptControllerTest()
{
//create new dbcontext
DbContextOptions<ApptSystemContext> options;
var builder = new DbContextOptionsBuilder<ApptSystemContext>();
builder.UseInMemoryDatabase();
options = builder.Options;
var context = new ApptSystemContext(options);
//instantiate your controller
var controller = new appController(context);
//call your method that you want to test
var retVal = controller.assignAppt(args go here);
}
将方法的主体更改为此:
public string assignAppt([FromBody] dynamic apptData)
{
int id = apptData.SlotId;
string json = apptData.ApptJson;
DateTime timeStamp = DateTime.Now;
using (_context)
{
var slot = _context.AppointmentSlots.Single(s => s.SlotId == id);
// make sure there isn't already an appointment booked in appt slot
if (slot.Timestamp == null)
{
slot.ApptJson = json;
slot.Timestamp = timeStamp;
_context.SaveChanges();
return "Task Executed\n";
}
else
{
return "There is already an appointment booked for this slot.\n" +
"If this slot needs changing try updating it instead of assigning it.";
}
}
}
另一个建议,除非绝对被迫这样做,否则不要将动态对象用作请求的主体。使用动态对象可以传递任何内容,并且您将无法确定请求是否可以接受。