Làm thế nào để giả lập ModelState.IsValid bằng cách sử dụng khung công tác Moq?


91

Tôi đang kiểm tra ModelState.IsValidphương thức hành động bộ điều khiển của mình để tạo một Nhân viên như sau:

[HttpPost]
public virtual ActionResult Create(EmployeeForm employeeForm)
{
    if (this.ModelState.IsValid)
    {
        IEmployee employee = this._uiFactoryInstance.Map(employeeForm);
        employee.Save();
    }

    // Etc.
}

Tôi muốn mô phỏng nó trong phương pháp kiểm tra đơn vị của mình bằng Moq Framework. Tôi đã cố gắng chế nhạo nó như thế này:

var modelState = new Mock<ModelStateDictionary>();
modelState.Setup(m => m.IsValid).Returns(true);

Nhưng điều này ném ra một ngoại lệ trong trường hợp thử nghiệm đơn vị của tôi. Có ai có thể giúp tôi ngoài này không?

Câu trả lời:


142

Bạn không cần phải chế nhạo nó. Nếu bạn đã có bộ điều khiển, bạn có thể thêm lỗi trạng thái mô hình khi khởi tạo thử nghiệm của mình:

// arrange
_controllerUnderTest.ModelState.AddModelError("key", "error message");

// act
// Now call the controller action and it will 
// enter the (!ModelState.IsValid) condition
var actual = _controllerUnderTest.Index();

làm cách nào để chúng ta đặt ModelState.IsValid thành đúng trường hợp? ModelState không có setter và do đó chúng ta không thể làm như sau: _controllerUnderTest.ModelState.IsValid = true. Nếu không có điều đó, nó sẽ không đánh người lao động
Karan

4
@Newton, nó đúng theo mặc định. Bạn không cần phải chỉ định bất cứ điều gì để đánh đúng trường hợp. Nếu bạn muốn gặp trường hợp sai, bạn chỉ cần thêm lỗi modelstate như được hiển thị trong câu trả lời của tôi.
Darin Dimitrov

IMHO Giải pháp tốt hơn là sử dụng băng tải mvc. Bằng cách này, bạn có được hành vi thực tế hơn của bộ điều khiển của mình, bạn nên cung cấp xác thực mô hình cho xác thực thuộc tính - định mệnh của nó. Bài đăng dưới đây mô tả điều này ( stackoverflow.com/a/5580363/572612 )
Vladimir Shmidt

13

Vấn đề duy nhất tôi gặp phải với giải pháp ở trên là nó không thực sự kiểm tra mô hình nếu tôi đặt thuộc tính. Tôi thiết lập bộ điều khiển của mình theo cách này.

private HomeController GenerateController(object model)
    {
        HomeController controller = new HomeController()
        {
            RoleService = new MockRoleService(),
            MembershipService = new MockMembershipService()
        };
        MvcMockHelpers.SetFakeAuthenticatedControllerContext(controller);

        // bind errors modelstate to the controller
        var modelBinder = new ModelBindingContext()
        {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
            ValueProvider = new NameValueCollectionValueProvider(new NameValueCollection(), CultureInfo.InvariantCulture)
        };
        var binder = new DefaultModelBinder().BindModel(new ControllerContext(), modelBinder);
        controller.ModelState.Clear();
        controller.ModelState.Merge(modelBinder.ModelState);
        return controller;
    }

Đối tượng modelBinder là đối tượng kiểm tra tính hợp lệ của mô hình. Bằng cách này, tôi có thể đặt các giá trị của đối tượng và kiểm tra nó.


1
Rất tốt, đây chính xác là những gì tôi đang tìm kiếm. Tôi không biết có bao nhiêu người gửi câu hỏi cũ như thế này nhưng nó có giá trị đối với bạn. Cảm ơn.
W.Jackson

Có vẻ như là một giải pháp tuyệt vời, vẫn còn trong năm 2016 :)
Matt

2
Không phải tốt hơn là kiểm tra mô hình một cách riêng biệt với một cái gì đó như thế này? stackoverflow.com/a/4331964/3198973
RubberDuck

2
Mặc dù đây là một giải pháp thông minh, nhưng tôi đồng ý với @RubberDuck. Để đây là một thử nghiệm đơn vị thực tế, riêng biệt, việc xác nhận mô hình phải là thử nghiệm của chính nó, trong khi thử nghiệm bộ điều khiển phải có các thử nghiệm riêng. Nếu mô hình thay đổi để vi phạm xác nhận ModelBinder, kiểm tra bộ điều khiển của bạn sẽ không thành công, đây là một dương tính giả vì logic bộ điều khiển không bị hỏng. Để kiểm tra ModelStateDictionary không hợp lệ, chỉ cần thêm lỗi ModelState giả để kiểm tra ModelState.IsValid không thành công.
xDaevax

2

Câu trả lời của uadrive đã giúp tôi đi được một phần chặng đường, nhưng vẫn còn một số khoảng trống. Không có bất kỳ dữ liệu nào trong đầu vào new NameValueCollectionValueProvider(), chất kết dính mô hình sẽ liên kết bộ điều khiển với một mô hình trống, không phải với modelđối tượng.

Điều đó tốt - chỉ cần tuần tự hóa mô hình của bạn dưới dạng a NameValueCollection, và sau đó chuyển nó vào hàm NameValueCollectionValueProvidertạo. Chà, không hoàn toàn. Thật không may, nó không hoạt động trong trường hợp của tôi vì mô hình của tôi có chứa một bộ sưu tập và NameValueCollectionValueProvidernó không phù hợp với các bộ sưu tập.

Tuy nhiên, JsonValueProviderFactorysự giải cứu đến ở đây. Nó có thể được sử dụng DefaultModelBindermiễn là bạn chỉ định loại nội dung là "application/json"và chuyển đối tượng JSON được tuần tự hóa của bạn vào luồng đầu vào của yêu cầu của bạn (Xin lưu ý, vì luồng đầu vào này là luồng bộ nhớ, bạn có thể để nó không bị tranh cãi, như một bộ nhớ luồng không giữ bất kỳ tài nguyên bên ngoài nào):

protected void BindModel<TModel>(Controller controller, TModel viewModel)
{
    var controllerContext = SetUpControllerContext(controller, viewModel);
    var bindingContext = new ModelBindingContext
    {
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => viewModel, typeof(TModel)),
        ValueProvider = new JsonValueProviderFactory().GetValueProvider(controllerContext)
    };

    new DefaultModelBinder().BindModel(controller.ControllerContext, bindingContext);
    controller.ModelState.Clear();
    controller.ModelState.Merge(bindingContext.ModelState);
}

private static ControllerContext SetUpControllerContext<TModel>(Controller controller, TModel viewModel)
{
    var controllerContext = A.Fake<ControllerContext>();
    controller.ControllerContext = controllerContext;
    var json = new JavaScriptSerializer().Serialize(viewModel);
    A.CallTo(() => controllerContext.Controller).Returns(controller);
    A.CallTo(() => controllerContext.HttpContext.Request.InputStream).Returns(new MemoryStream(Encoding.UTF8.GetBytes(json)));
    A.CallTo(() => controllerContext.HttpContext.Request.ContentType).Returns("application/json");
    return controllerContext;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.