5

I found the following post which describes how to test a custom model binder:

https://stackoverflow.com/a/55387164/155899

However, this tests an individual model binder. Ideally I'd like to be able to register all of my binders and test that the appropriate model was bound. This is how I would've achieved what I'm looking for in ASP.NET MVC:

// Register the model binders
ModelBinders.Binders[typeof(DateTime)] = new DateTimeModelBinder();
...

var values = new NameValueCollection {
    { "Foo", "1964/12/02 12:00:00" }
};

var controllerContext = CreateControllerContext(); // Utility method
var bindingContext = new ModelBindingContext() {
    ModelName = "Foo",
    ValueProvider = new NameValueCollectionValueProvider(values, null)
};
var binder = ModelBinders.Binders.GetBinder(typeof(DateTime));

var result = (DateTime)binder.BindModel(controllerContext, bindingContext);

Not only does this allow me to test the result of my model binder but it also makes sure the correct model binder is selected.

I'd appreciate it if someone could help. Thanks

7
  • 3
    Note that if you test that the correct model binder is selected, you're testing Microsoft's code. That's fine, but a little redundant. You can see how they test model binders in the code. Commented May 3, 2019 at 19:50
  • 1
    The reason I need to do this is because as far as I can tell, the framework will try to select the first model binder which is supported for the model type. Therefore if I did not register my model binder, it was registered in the wrong order or I build a newer model binder which accidentally overrides it then I need to make sure this fails. Commented May 3, 2019 at 20:10
  • 1
    An integration test using test server would perhaps work better for you then Commented May 3, 2019 at 20:12
  • @HereticMonkey - thanks, I’ve been playing around with this idea. However I had to copy so many different classes and utility methods that I gave up as it’s become a mess. Commented May 4, 2019 at 9:36
  • @VidmantasBlazevicius thanks, I did investigate writing an integration test (using a test server) however from my understanding I would have to expose an end point with my model as a parameter. Please correct me if I’m wrong. Commented May 4, 2019 at 9:37

1 Answer 1

6

I've managed to put something together. Thanks to @HereticMonkey for the link which helped me out.

[Fact]
public async Task Test() {
    // Arrange
    var services = new ServiceCollection();
    services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
    services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
    services.AddMvc(o => {
        o.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
    });
    var serviceProvider = services.BuildServiceProvider();

    var options = serviceProvider.GetService<IOptions<MvcOptions>>();
    var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(new List<IMetadataDetailsProvider>());
    var metadataProvider = new DefaultModelMetadataProvider(compositeDetailsProvider);
    var modelBinderFactory = new ModelBinderFactory(metadataProvider, options, serviceProvider);

    var parameterBinder = new ParameterBinder(
        metadataProvider,
        modelBinderFactory,
        new Mock<IObjectModelValidator>().Object,
        options,
        NullLoggerFactory.Instance);

    var parameter = new ParameterDescriptor() {
        Name = "parameter",
        ParameterType = typeof(DateTime)
    };

    var controllerContext = new ControllerContext() {
        HttpContext = new DefaultHttpContext() {
            RequestServices = serviceProvider // You must set this otherwise BinderTypeModelBinder will not resolve the specified type
        },
        RouteData = new RouteData()
    };

    var modelMetadata = metadataProvider.GetMetadataForType(parameter.ParameterType);

    var formCollection = new FormCollection(new Dictionary<string, StringValues>() {
        { "Foo", new StringValues("1964/12/02 12:00:00") }
    });
    var valueProvider = new FormValueProvider(BindingSource.Form, formCollection, CultureInfo.CurrentCulture);

    var modelBinder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext() {
        BindingInfo = parameter.BindingInfo,
        Metadata = modelMetadata,
        CacheToken = parameter
    });

    // Act
    var modelBindingResult = await parameterBinder.BindModelAsync(
        controllerContext,
        modelBinder,
        valueProvider,
        parameter,
        modelMetadata,
        value: null);

    // Assert
    Assert.True(modelBindingResult.IsModelSet);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.