TableControllers单元测试

时间:2017-01-18 20:01:16

标签: c# unit-testing azure

所以我正在尝试为我的后端编写一个简单的tablecontroller单元测试?

我还没有能够这样做,我所能实现的只是为ApiControllers编写单元测试,但有没有办法为TableControllers编写单元测试?

我想做的是:

public class AuctionController : TableController<Auction>
{
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
        MobileServiceContext context = new MobileServiceContext();
        DomainManager = new EntityDomainManager<Auction>(context, Request);
    }

    // GET tables/Auction
    public IQueryable<Auction> GetAllAuction()
    {
        return Query(); 
    }

    // GET tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public SingleResult<Auction> GetAuction(string id)
    {
        return Lookup(id);
    }

    // PATCH tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public Task<Auction> PatchAuction(string id, Delta<Auction> patch)
    {
        return UpdateAsync(id, patch);
    }

    // POST tables/Auction
    public async Task<IHttpActionResult> PostAuction(Auction item)
    {
        Auction current = await InsertAsync(item);
        return CreatedAtRoute("Tables", new { id = current.Id }, current);
    }

    // DELETE tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public Task DeleteAuction(string id)
    {
        return DeleteAsync(id);
    }
}

我想制作一个像这样的测试控制器:

[TestClass]
public class AuctionControllerTests
{
    private readonly AuctionController _controller;

    public AuctionControllerTests()
    {
        _controller = new AuctionController();
    }

    [TestMethod]
    public void Fetch_all_existing_items()
    {
        Assert.Equal(2, _controller.GetAllTodoItems().ToList().Count);
    }
}

我怎么能够让这个工作?我非常感谢你的帮助。

2 个答案:

答案 0 :(得分:2)

是。它是可能的,但你的代码不是单元可测试的。以下是您的步骤

  1. 找到一种方法注入您的依赖项MobileServiceContext和DomainManager
  2. 您需要设置上下文和请求等,如下面的代码所示。
  3. (代码假设您正在使用Moq)

    series: [{
            type: 'line',
            data: [1,2,3,4,5,6,7,5,4,3,9,7],
            name: 'Temperature'
        }, {
            data: [0,1,0,0,0,0,1,0,1,0,0,0],
            name: 'Rain'
        },{
            data: [0,1,0,0,0,0,1,0,0,0,0,0],
            name: 'Snow'
        }]
    

    继承此代码并确保在测试执行之前调用TargetSetup()(可能在测试初始化​​(设置)中。并且您可以像以下一样进行调用:

    public class ControllerUnitTestBase<T> where T: Controller      
    {
        private Action<RouteCollection> _routeRegistrar;
        private Mock<HttpRequestBase> _mockRequest;
    
        protected virtual Action<RouteCollection> RouteRegistrar
        {
            get { return _routeRegistrar ?? DefaultRouteRegistrar; }
            set { _routeRegistrar = value; }
        }
    
        protected Mock<HttpRequestBase> MockRequest
        {
            get
            {
                if (_mockRequest == null)
                {
                    _mockRequest = new Mock<HttpRequestBase>();
                }
    
                return _mockRequest;
            }
        }
    
        public abstract T TargetController { get; }
    
        protected void TargetSetup()
        {
            var routes = new RouteCollection();
            RouteRegistrar(routes);
    
            var responseMock = new Mock<HttpResponseBase>();
            responseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string url) => url);
    
            var contextMock = new Mock<HttpContextBase>();
            contextMock.SetupGet(x => x.Request).Returns(MockRequest.Object);
            contextMock.SetupGet(x => x.Response).Returns(responseMock.Object);
            contextMock.SetupGet(x => x.Session).Returns(Mock<HttpSessionStateBase>().Object);
    
            TargetController.ControllerContext = new ControllerContext(contextMock.Object, new RouteData(), TargetController);
            TargetController.Url = new UrlHelper(new RequestContext(contextMock.Object, new RouteData()), routes);
        }
    
        protected void DefaultRouteRegistrar(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
        }
     }
    

答案 1 :(得分:2)

非常感谢这个模拟解决方案,它工作正常,但是我在不使用模拟框架的情况下编写了一个通用的更好的解决方案,稍后我将应用模拟框架,现在我将坚持使用fakes和real dbs进行集成测试。

但是第一次我写了一个Generic TableController,以便为那些有多个Context的人应用多个EntityData和DbContext,也可以应用FakeContext,这要归功于接口的抽象,但我还没有应用到这个例子。

首先这是我的BaseController:

//This is an abstract class so we can apply inheritance to scalfolded tablecontrollers<T>.
public abstract class BaseController<TModel, TDbContext> : TableController<TModel> where TModel : class, ITableData
                                                                                       where TDbContext:DbContext, new()
{
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
        var context = new TDbContext();
        SetDomainManager(new EntityDomainManager<TModel>(context, Request));
    }

    public void SetDomainManager(EntityDomainManager<TModel> domainManager)
    {
        DomainManager = domainManager;
    }
}

这是我的scalfolded控制器,我的basecontroller应用!!!

public class AuctionController : BaseController<Auction, MobileServiceContext>    
{        
    public IQueryable<Auction> GetAllAuction()
    {
        return Query(); 
    }

    // GET tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public SingleResult<Auction> GetAuction(string id)
    {
        return Lookup(id);
    }

    // PATCH tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public Task<Auction> PatchAuction(string id, Delta<Auction> patch)
    {
         return UpdateAsync(id, patch);
    }

    // POST tables/Auction
    public async Task<IHttpActionResult> PostAuction(Auction item)
    {
        Auction current = await InsertAsync(item);
        return CreatedAtRoute("Tables", new { id = current.Id }, current);
    }

    // DELETE tables/Auction/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public Task DeleteAuction(string id)
    {
         return DeleteAsync(id);
    }
}

使用我的通用应用程序,我可以应用任何DbContext,甚至可以应用FakeDbContexts以避免SqlConnection或云连接,例如Azure,这是我在本例中使用的。

2018年3月14日更新

所有这两个库都在我的Backend项目中,现在我将向您展示我的测试项目以便单元测试TableController

 public abstract class ControllerTestBase<TController, TModel, TDbContext> where TController : BaseController<TModel, TDbContext>, new() 
                                                                           where TModel : class, ITableData
                                                                           where TDbContext: DbContext, new()
{
    protected readonly TController Controller;

    protected ControllerTestBase()
    {   
        Controller = new TController();
        Controller.Configuration = new HttpConfiguration();
        Controller.Request = new HttpRequestMessage();
        var context = new TDbContext();        
        Controller.SetDomainManager(new EntityDomainManager<TModel>(context, Controller.Request));
    }
}

好的,感谢这个抽象类,您可以从测试库中提取初始化设置,因为每次运行测试时,它都会调用通用测试构造函数,设置所有必要的需求,从而避免ArgumentNullExceptions和InvalidOperationExceptions这样的常见问题。单元测试tablecontroller因为非常直观地初始化为ApiController。

最后,如果你修改它,那么你可以运行这样的测试:

[TestClass]
public class AuctionControllerTest : ControllerTestBase<AuctionController, Auction, MobileServiceContext>
{
    [TestMethod]
    public void Fetch_All_Existing_Items()
    {
        Assert.AreEqual(1, Controller.GetAllAuction().ToList().Count);
    }
}

由于我的通用应用程序,您现在可以使用此代码作为示例应用于TableControllers,并且如果您遵循接口隔离原则,您可以将FakeDbContext应用于您的控制器。

对于那些帮助过我的人,谢谢你们开始考虑使用这个解决方案!!!