aspnet / Mvc

[Archived] ASP.NET Core MVC is a model view controller framework for building dynamic web sites with clean separation of concerns, including the merged MVC, Web API, and Web Pages w/ Razor. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
5.61k stars 2.14k forks source link

TryValidateModel throws exception when unit tested #6000

Closed michaelbowman1024-zz closed 7 years ago

michaelbowman1024-zz commented 7 years ago

I have what seems to be a very similar issue to issue #3586, where TryValidateModel throws an Object reference error when unit tested. During normal operation with similar data being passed into the controller, the validation passes. Here is the controller method:

    public class LocationsController : Controller
    {
        [HttpPut("{id}")]
        public IActionResult PartiallyUpdateLocation(int id,
            [FromBody] JsonPatchDocument<LocationForUpdateDTO> patchDoc)
        {
            if (patchDoc == null)
            {
                return BadRequest();
            }

            var locationEntity = _locationsRepository.GetLocation(id);
            if (locationEntity == null)
            {
                return NotFound();
            }

            var locationToPatch = Mapper.Map<LocationForUpdateDTO>(locationEntity);

            patchDoc.ApplyTo(locationToPatch, ModelState);

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            **FAILS HERE**
            TryValidateModel(locationToPatch);

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var finalLocation = Mapper.Map(locationToPatch, locationEntity);

            _locationsRepository.PartiallyUpdate(id, finalLocation);
            return NoContent();
        }
    }

...and here is the unit test implementation:

    [Collection("AutoMapper collection")]
    public class LocationsController_PatchLocationTests
    {
        AutoMapperConfigFixture fixture;

        public LocationsController_PatchLocationTests(AutoMapperConfigFixture fixture)
        {
            this.fixture = fixture;
        }

        [Fact]
        public void PatchLocationReturnsNoContent()
        {
            var repositoryMock = GetMockRepositoryHelper(1);
            LocationsController controller = new LocationsController(repositoryMock);
            var patchDoc = new JsonPatchDocument<LocationForUpdateDTO>();
            patchDoc.Replace(l => l.Street, TestDataForPartialUpdate.goodLocation.Street);
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Location, LocationForUpdateDTO>();
            });

            var result = controller.PartiallyUpdateLocation(1, patchDoc);

            Assert.IsType(typeof(NoContentResult), result);
        }

        private static ILocationsRepository GetMockRepositoryHelper(int id)
        {
            Mock<ILocationsRepository> repositoryMock = new Mock<ILocationsRepository>();
            repositoryMock.Setup(x => x.GetLocation(id))
                .Returns(
                    new Location()
                    {
                        Id = 1,
                        Street = "123 AppleTree Way",
                        City = "Galloway",
                        State = "Ohio",
                        ZipCode = "43119",
                        Latitude = 40.0,
                        Longitude = 80.0
                    });

            return repositoryMock.Object;
        }
    }

As well as the Automapper CollectionDefinition:

    [CollectionDefinition("AutoMapper collection")]
    public class AutoMapperConfig : ICollectionFixture<AutoMapperConfigFixture>
    {

    }

The stack trace:

at Microsoft.AspNetCore.Mvc.ControllerBase.TryValidateModel(Object model, String prefix)
at LocationsAPI.Controllers.LocationsController.PartiallyUpdateLocation(Int32 id, JsonPatchDocument`1 patchDoc) in editedpath\src\LocationsAPI\Controllers\LocationsController.cs:line 138
at LocationsAPI.Tests.Unit.LocationsController_PatchLocationTests.PatchLocationReturnsNoContent() in editedpath\test\LocationsAPI.Tests.Unit\LocationsController_PatchLocationTests.cs:line 36

rynowak commented 7 years ago

You'll need to set controller.ObjectValidator when trying to test anything that calls TryValidateModel. I wonder if there's anything we can do to make this better.

rynowak commented 7 years ago

Closing because this appears to be answered. If this doesn't help please reactivate or open a new issue

hanslai commented 6 years ago

@rynowak I was just having the same issue. while search for answer land me on this page. even adding the code below solve the problem. But it does not feel right that we need to do that in order to use TryValidateModel for unit testing.

and this knowledge (or information) is not mentioned in the document of ControllerBase.TryValidateModel

It does not feel right because this piece of code is not within any of our testing logic. If we cannot improve upon this, we should at least mention this in the docs of TryValidateModel.

var objectValidator = new Mock<IObjectModelValidator>();
            objectValidator.Setup(o => o.Validate(It.IsAny<ActionContext>(),
                                              It.IsAny<ValidationStateDictionary>(),
                                              It.IsAny<string>(),
                                              It.IsAny<Object>()));
            _sut.ObjectValidator = objectValidator.Object;