4

I've set up a Web API project using Ninject, and I've used the fix detailed here for getting it to work with the latest version of the Web API. Everything is working fine, but I'm now trying to write some tests.

I'm using in-memory hosting to run the project for the tests, as detailed here, as I have a DelegatingHandler that performs authentication and then sets a property on the request message that is used by all the Api Controllers.

So, I've got a base class for my tests, and have a SetUp method where I set up the HttpServer and configuration, which I've pretty much taken from my working Ninject code:

[SetUp]
public void Setup()
{
    bootstrapper = new Bootstrapper();

    DynamicModuleUtility.RegisterModule(
        typeof(OnePerRequestHttpModule));

    DynamicModuleUtility.RegisterModule(
        typeof(NinjectHttpModule));

    bootstrapper.Initialize(CreateKernel);

    var config = new HttpConfiguration();
    config.Routes.MapHttpRoute("Login",
        "api/auth/token",
        new { controller = "Users", action = "Login" });

    config.IncludeErrorDetailPolicy =
        IncludeErrorDetailPolicy.Always;

    config.DependencyResolver = 
        new NinjectResolver(CreateKernel());

    config.MessageHandlers.Add(
        new AuthenticationHandler(CreateUserManager()));

    Server = new HttpServer(config);
}

This is how I create the MoqMockingKernel:

private static IKernel CreateKernel()
{
    var kernel = new MoqMockingKernel();
    kernel.Bind<Func<IKernel>>()
        .ToMethod(ctx => () => new Bootstrapper().Kernel);
    kernel.Bind<IHttpModule>()
        .To<HttpApplicationInitializationHttpModule>();

    RegisterServices(kernel);

    GlobalConfiguration.Configuration.DependencyResolver = 
        new NinjectResolver(kernel);

    return kernel;
}

And this is how I register the objects to use:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IUserManager>().ToMock();

    kernel.Bind<UsersController>().ToSelf();
}

While I'm not testing the Controller per se, I do want a proper instance of it to be called, which is why I'm binding it ToSelf. I must admit that I am assuming that this is correct. This is an example of a test:

public void UserCannotLogin()
{
    System.Net.Http.HttpClient client = 
        new System.Net.Http.HttpClient(Server);

    string json = string.Format(
        "{{ \"Username\": \"{0}\", \"Password\": \"{1}\" }}", 
        "wrong", "wrong");

    HttpRequestMessage request = 
        CreateRequest(@"api/auth/token", json, HttpMethod.Get);

    Action action = () => client.SendAsync(request);

    using (var response = client.SendAsync(request).Result)
    {
        response.StatusCode.Should()
            .Be(HttpStatusCode.Unauthorized);
    }
}

I'm basically getting a 404 error. When I debug it, it does go to my DelegatingHandler, but it doesn't go to my controller.

I get the feeling that I'm fundamentally missing a point here, and it may not even be possible to do what I'm trying to do, but if anyone has any suggestions for either how to do this, or a different way to achieve the same thing, I'm all ears.

Update I think that it's because the default behaviour of the MockingKernel is to provide a Mock unless told otherwise, so it is returning a Mock of IHttpControllerSelector. I've set up a couple of default ones now:

kernel.Bind<IHttpControllerSelector>()
    .To<DefaultHttpControllerSelector>();
kernel.Bind<IContentNegotiator>()
    .To<DefaultContentNegotiator>();

It's still not working, I think because there are no formatters specified. I'll try that tomorrow and see if that gets me there.

4

1 回答 1

1

好的,当我说我在这里从根本上错过了一点时,我认为我是正确的,但我会回答这个问题,以防它帮助其他人避免同样的错误!

我认为, NinjectMockingKernel主要是关于自动模拟,所以如果你有很多接口,你不关心它们在测试中是如何设置的,你可以在测试中忽略它们,它们会自动创建用于你。

在 Web API 的情况下,绝对不是这种情况,因为您不希望控制器选择器类被自动模拟,否则您最终不会调用您的控制器。

所以,我想出的解决方案是坚持使用标准的 Ninject Kernel,然后将你的接口绑定到一个常量 Mock 对象:

kernel.Bind<IUserManager>().ToConstant(CreateUserManager());

private IUserManager CreateUserManager()
{
    Mock<IUserManager> userManager = new Mock<IUserManager>();

    // Set up the methods you want mocked

    return userManager.Object;
}

这样做,我已经能够成功编写使用 HttpClient 调用内存中 HttpServer 的测试,该 HttpServer 成功调用我的DelegatingHandler然后最终到达我的控制器。

于 2012-08-14T10:38:32.850 回答