Owin.TestServer错误地在Web Api 2

时间:2016-12-07 19:01:05

标签: asp.net-web-api integration-testing owin

我正在使用内存中的Owin TestServer为WebApi设置集成测试并收到错误

  

找到了多个匹配名为“values”的控制器的类型。   如果为此请求提供服务的路由,则会发生这种情况   ('api / {controller}')发现了多个控制器   同名但不同的名称空间,不受支持

在DefaultHttpControllerTypeResolver.GetControllerTypes()。

以下是指向再现错误的small project的链接。

修改
为了解决这个问题我随后尝试使用普通的HttpServer,HttpSelfHostServer和Owin.SelfHost以及Owin.TestServer,但他们都遇到了同样的问题。
修改编辑

模式是,当批量运​​行测试时,第一个测试通过,但后续测试失败并显示“找到多个类型”错误。这发生在我的实际项目以及提供的简单repro项目中,它提供了完全相同的行为。但是正如您在repro项目中看到的那样,只有一个ValuesController,尽管错误消息另有声明,并且我的实际项目中也不存在多个控制器类型。

我的主项目中有一个集成测试的基类,可以使用派生类使用Owin TestServer对实际的IIS服务器和内存运行。当我对IIS运行测试时,它们都通过了;当我使用Owin TestServer单独运行测试时,它们都通过了;但是当我在内存中作为批处理运行它们时,第一个通过,但其余的总是因上述错误而失败。

为了在每次测试之前获得一个新的TestServer,我在TestInitialize()中创建了一个新的,但这并不能解决问题。

鉴于在针对IIS运行时所有测试都通过,我认为它们应该在使用Owin Test Server运行时通过。

以下是相关代码的摘要: WebApi是Visual Studio 2015创建的一个非常简单的开箱即用项目。

以下是用于创建Owin.TestServer

的Startup类
public class Startup
{        
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration();

        var tracing = config.EnableSystemDiagnosticsTracing();
        tracing.IsVerbose = true;
        tracing.MinimumLevel = System.Web.Http.Tracing.TraceLevel.Debug;           
        // The assembly resolver is shown below.
        config.Services.Replace(typeof(IAssembliesResolver), new TestApiAssemblyResolver());            

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Formatters.Remove(config.Formatters.XmlFormatter);
        app.UseWebApi(config);  

    }
}

测试初始化​​看起来像这样......

[TestInitialize()]
public void MyTestInitialize()
{
    server = TestServer.Create<Startup>();            
    this.HttpClient = server.HttpClient;
}

以下是用于将WebApi程序集加载到测试中的程序集解析程序。如果没有这个,测试会因Http NotFound错误而失败。

public class TestApiAssemblyResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {
        List<Assembly> baseAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "webapiCheck.dll");
        /*
         You can try unreferencing webapiCheck.dll from this project but it makes no difference.
         */
        //var path = @"Your path...Documents\visual studio 2015\Projects\WebApiCheck\WebApiCheck\bin\webapiCheck.dll";

        var controllersAssembly = Assembly.LoadFrom(path);
        baseAssemblies.Add(controllersAssembly);
        return baseAssemblies;
    }

}

这是一种测试方法......

[TestMethod]
public async Task GetValues()
{
    uri = "api/values";
    var response = await GetAsync();            

    if (response.IsSuccessStatusCode)
    {
        var result = await response.Content.ReadAsAsync<IEnumerable<string>>();
        Assert.AreEqual(2, result.Count());
    }
    else
    {
        Assert.Fail(response.ReasonPhrase);
    }

}

我尝试更换DefaultHttpControllerSelector以解决问题,但也遇到错误,因为base.GetControllerMapping()有时不会返回结果。

public class BypassCacheSelector : DefaultHttpControllerSelector
    {
        private readonly HttpConfiguration _configuration;

        public BypassCacheSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var path = ConfigurationManager.AppSettings["ApiAssemblyLocation"];

            var assembly = Assembly.LoadFrom(path);
            var types = assembly.GetTypes(); //GetExportedTypes doesn't work with dynamic assemblies
            var matchedTypes = types.Where(i => typeof(IHttpController).IsAssignableFrom(i)).ToList();

            // base.GetControllerMapping() sometimes returns no result;
            var map = base.GetControllerMapping();            

            string absPath = request.RequestUri.AbsolutePath;
            var candidateName = absPath.Substring(absPath.LastIndexOf("/")+1);
            string controllerName = null;
            foreach (var m in map)
            {
                if (candidateName.StartsWith(m.Key))
                {
                    controllerName = m.Key;
                    break;
                }                
            }

            var matchedController =
                matchedTypes.FirstOrDefault(i => i.Name.ToLower() == controllerName.ToLower() + "controller");

            return new HttpControllerDescriptor(_configuration, controllerName, matchedController);
        }
    }

为了确认问题与属性路由无关,我从repro项目中删除了属性路由,实际上问题仍然存在:当单独运行时,测试通过,但是当批量运行时,它们会以相同的错误失败。

如果有人可以指出如何使用WebApi正确使用Owin.TestServer,我会很感激。

1 个答案:

答案 0 :(得分:0)

问题出在TestApiAssemblyResolver中。在第二次和后续测试中,controllersAssembly被多次加载到baseAssemblies中,因此Controller Selector无法决定选择哪一个。解决方案是检查baseAssemblies,如果它不在baseAssemblies中,则只添加controllersAssembly。

以下是更新的代码,现在可以正常使用。

public class TestApiAssemblyResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {
        List<Assembly> baseAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

        var path = ConfigurationManager.AppSettings["ApiAssemblyLocation"];

        var controllersAssembly = Assembly.LoadFrom(path);
        // this next line fixed the problem: the second and subsequent time
        // the controller assembly was in baseAssemblies multiple times
        // which caused the problem.
        if (!baseAssemblies.Contains(controllersAssembly))
        {
            baseAssemblies.Add(controllersAssembly);
        }            

        return baseAssemblies;
    }

}