Câu hỏi Các bài kiểm tra đơn vị về xác thực MVC


Làm thế nào tôi có thể kiểm tra rằng hành động điều khiển của tôi là đặt các lỗi chính xác trong ModelState khi xác nhận thực thể, khi tôi đang sử dụng xác thực DataAnnotation trong MVC 2 Preview 1?

Một số mã để minh họa. Đầu tiên, hành động:

    [HttpPost]
    public ActionResult Index(BlogPost b)
    {
        if(ModelState.IsValid)
        {
            _blogService.Insert(b);
            return(View("Success", b));
        }
        return View(b);
    }

Và đây là một bài kiểm tra đơn vị không thành công mà tôi nghĩ là nên vượt qua nhưng không phải là (sử dụng MbUnit & Moq):

[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);

    // act
    var p = new BlogPost { Title = "test" };            // date and content should be required
    homeController.Index(p);

    // assert
    Assert.IsTrue(!homeController.ModelState.IsValid);
}

Tôi đoán ngoài câu hỏi này, Nên Tôi đang thử nghiệm xác nhận, và tôi có nên thử nghiệm nó theo cách này không?


75
2017-08-13 02:20


gốc


Không phải là var p = new BlogPost {Title = "test"}; nhiều hơn Arrange hơn Act? - RichardOD
nhún vai thế nào? - Matthew Groves
Assert.IsFalse (homeController.ModelState.IsValid); - seth flowers


Các câu trả lời:


Thay vì đi qua BlogPost bạn cũng có thể khai báo tham số hành động FormCollection. Sau đó, bạn có thể tạo BlogPost chính bạn và gọi UpdateModel(model, formCollection.ToValueProvider());.

Điều này sẽ kích hoạt xác thực cho bất kỳ trường nào trong FormCollection.

    [HttpPost]
    public ActionResult Index(FormCollection form)
    {
        var b = new BlogPost();
        TryUpdateModel(model, form.ToValueProvider());

        if (ModelState.IsValid)
        {
            _blogService.Insert(b);
            return (View("Success", b));
        }
        return View(b);
    }

Chỉ cần đảm bảo rằng thử nghiệm của bạn thêm giá trị null cho mọi trường trong biểu mẫu lượt xem mà bạn muốn để trống.

Tôi thấy rằng làm theo cách này, với chi phí của một vài dòng mã bổ sung, làm cho các xét nghiệm đơn vị của tôi giống với cách mã được gọi trong thời gian chạy chặt chẽ hơn làm cho chúng có giá trị hơn. Ngoài ra, bạn có thể kiểm tra những gì sẽ xảy ra khi ai đó nhập "abc" trong một điều khiển liên kết với thuộc tính int.


-3
2017-08-13 07:32



Tôi thích cách tiếp cận này, nhưng nó có vẻ như một bước lùi, hoặc ít nhất một bước phụ mà tôi phải đưa vào mỗi hành động xử lý POST. - Matthew Groves
Tôi đồng ý. Nhưng có các bài kiểm tra đơn vị của tôi và ứng dụng thực sự hoạt động theo cùng một cách là đáng để nỗ lực. - Maurice
Cách tiếp cận ARM là IMHO tốt hơn :) - kamranicus
Kiểu đánh bại mục đích của MVC. - Andy
Tôi đồng ý rằng câu trả lời của ARM là tốt hơn. Việc chuyển một FormCollection sang một hành động điều khiển là không mong muốn, so với việc truyền một đối tượng Model / ViewModel được đánh máy mạnh. - Alex York


Ghét để necro một bài cũ, nhưng tôi nghĩ rằng tôi muốn thêm suy nghĩ của riêng tôi (kể từ khi tôi chỉ có vấn đề này và chạy qua bài đăng này trong khi tìm kiếm câu trả lời).

  1. Không kiểm tra xác nhận trong các kiểm tra bộ điều khiển của bạn. Bạn có thể tin cậy xác thực của MVC hoặc viết của riêng bạn (nghĩa là không kiểm tra mã của người khác, kiểm tra mã của bạn)
  2. Nếu bạn muốn kiểm tra xác nhận đang làm những gì bạn mong đợi, hãy kiểm tra nó trong các thử nghiệm mô hình của bạn (tôi làm điều này cho một vài xác nhận hợp lệ regex phức tạp hơn).

Những gì bạn thực sự muốn thử nghiệm ở đây là bộ điều khiển của bạn làm những gì bạn mong đợi nó làm khi xác nhận không thành công. Đó là mã của bạn và mong đợi của bạn. Thử nghiệm nó rất dễ dàng khi bạn nhận ra đó là tất cả những gì bạn muốn thử nghiệm:

[test]
public void TestInvalidPostBehavior()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);
    var p = new BlogPost();

    homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.  
    // What I'm doing is setting up the situation: my controller is receiving an invalid model.

    // act
    var result = (ViewResult) homeController.Index(p);

    // assert
    result.ForView("Index")
    Assert.That(result.ViewData.Model, Is.EqualTo(p));
}

189
2017-09-28 19:02



Tôi đồng ý, đây sẽ là câu trả lời đúng. Như ARM nói: việc xác thực được xây dựng trong không nên được kiểm tra. Thay vào đó, hành vi của bộ điều khiển của bạn phải là điều được kiểm tra. Điều đó có ý nghĩa nhất. - Alex York
Bộ điều khiển nên được kiểm tra riêng biệt với mô hình ràng buộc và xác nhận. Theo cả hai KISS và tách mối quan tâm. Tôi đang thực hiện một loạt các bài viết về đơn vị kiểm tra các thành phần MVC tại đây timoch.com/blog/2013/06/… - TiMoch
Bạn nên làm gì để kiểm tra thuộc tính xác thực tùy chỉnh? Nếu những người đang được sử dụng, sau đó người ta không thể "tin tưởng xác nhận của MVC". Làm thế nào bạn sẽ kiểm tra (trong các thử nghiệm mô hình, có lẽ) rằng xác nhận tùy chỉnh đang hoạt động? - John Saunders
Tôi không đồng ý. Chúng tôi vẫn cần phải xác minh rằng một mô hình cụ thể sẽ tạo ra các lỗi mô hình được sử dụng như điều kiện tiên quyết trong thử nghiệm này. Tuy nhiên, mã ví dụ là câu trả lời hoàn hảo cho câu hỏi được xác định của bạn trong 1. Tuy nhiên, đó không phải là câu trả lời cho câu hỏi ban đầu - Ibrahim ben Salah
Đây không phải là thử nghiệm xác nhận mô hình. Trường hợp tại điểm, ai đó có thể (cố tình hoặc vô tình) xóa chú thích dữ liệu trong mô hình (có thể là lỗi kết hợp?) Và thử nghiệm này sẽ không thành công. - Rosdi Kasim


Tôi đã có cùng một vấn đề, và sau khi đọc câu trả lời và bình luận của Paul, tôi đã tìm kiếm một cách để xác nhận hợp lệ mô hình khung nhìn.

tôi đã tìm thấy hướng dẫn này giải thích cách xác thực một cách thủ công một ViewModel sử dụng DataAnnotations. Đoạn mã khóa chính là ở cuối bài đăng.

Tôi đã sửa đổi mã một chút - trong hướng dẫn tham số thứ 4 của TryValidateObject được bỏ qua (validateAllProperties). Để có được tất cả chú thích để Xác thực, điều này phải được đặt thành true.

Additionaly tôi đã tái cấu trúc mã thành một phương thức chung, để thực hiện kiểm tra xác thực ViewModel đơn giản:

    public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) 
        where TController : ApiController
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }

Cho đến nay điều này đã làm việc thực sự tốt cho chúng tôi.


83
2017-07-28 13:08



Rất tuyệt, tôi chắc chắn có thể sử dụng :) - Matthew Groves
Đẹp, nhưng chỉ hoạt động trong .NET 4 rõ ràng, không phải 3,5 - codeulike
Xin lỗi thậm chí không kiểm tra điều đó. Tất cả các dự án MVC của chúng tôi là 4.0 - Giles Smith
@EvanM nó là o.k. Tôi sẽ upvote cho bạn. Cảm ơn Giles! - gdoron
Tại sao cần phải sử dụng Generics? Điều này có thể được tiêu thụ dễ dàng hơn nhiều nếu nó được định nghĩa là: void ValidateViewModel (object viewModelToValidate, Controller controller) hoặc thậm chí tốt hơn như là một phương thức mở rộng: public static void ValidateViewModel (Controller controller, object viewModelToValidate) - Chad Grant


Khi bạn gọi phương thức homeController.Index trong bài kiểm tra của bạn, bạn không sử dụng bất kỳ khung công tác MVC nào để thoát khỏi quá trình xác nhận để ModelState.IsValid luôn đúng. Trong mã của chúng tôi, chúng tôi gọi phương thức xác thực của trình trợ giúp trực tiếp trong bộ điều khiển thay vì sử dụng xác thực môi trường. Tôi đã không có nhiều kinh nghiệm với DataAnnotations (Chúng tôi sử dụng NHibernate.Validators) có thể ai đó khác có thể cung cấp hướng dẫn làm thế nào để gọi Validate từ bên trong bộ điều khiển của bạn.


6
2017-08-13 03:58



Tôi thích thuật ngữ "xác nhận môi trường xung quanh". Nhưng phải có một cách để kích hoạt điều này trong một bài kiểm tra đơn vị mặc dù? - Matthew Groves
Vấn đề mặc dù là bạn về cơ bản thử nghiệm khuôn khổ MVC - không phải bộ điều khiển của bạn. Bạn đang cố gắng xác nhận rằng MVC đang xác thực mô hình của bạn như bạn mong đợi. Cách duy nhất để làm điều đó với bất kỳ sự chắc chắn nào là giả lập toàn bộ đường ống MVC và mô phỏng một yêu cầu web. Đó có lẽ là nhiều hơn bạn thực sự cần phải biết. Nếu bạn chỉ thử nghiệm rằng việc xác nhận dữ liệu trên các mô hình của bạn được thiết lập đúng, bạn có thể làm điều đó mà không cần bộ điều khiển và chỉ chạy xác thực dữ liệu theo cách thủ công. - Paul Alexander


Tôi đã nghiên cứu ngày hôm nay và tôi đã tìm thấy bài đăng trên blog này bởi Roberto Hernández (MVP) dường như cung cấp giải pháp tốt nhất để kích hoạt các trình duyệt tính hợp lệ cho một hành động điều khiển trong quá trình kiểm thử đơn vị. Điều này sẽ đặt các lỗi chính xác trong ModelState khi xác nhận thực thể.


3
2017-10-05 03:45





Tôi đang sử dụng ModelBinders trong các trường hợp thử nghiệm của mình để có thể cập nhật giá trị model.IsValid.

var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");

var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);

ViewResult result = (ViewResult)controller.Add(model);

Với phương thức MvcModelBinder.BindModel của tôi như sau (về cơ bản cùng một mã được sử dụng trong khuôn khổ MVC):

        public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
        {
            IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
            ModelBindingContext bindingContext = new ModelBindingContext()
            {
                FallbackToEmptyPrefix = true,
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
                ModelName = "NotUsedButNotNull",
                ModelState = controller.ModelState,
                PropertyFilter = (name => { return true; }),
                ValueProvider = valueProvider
            };

            return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
        }

2
2018-02-18 19:50



Điều này không hoạt động nếu bạn có nhiều hơn một thuộc tính xác thực trên một thuộc tính. Thêm dòng này controller.ModelState.Clear(); trước khi mã tạo ModelBindingContext và nó sẽ hoạt động - Suhas


Điều này không trả lời chính xác câu hỏi của bạn, bởi vì nó từ bỏ DataAnnotations, nhưng tôi sẽ thêm nó bởi vì nó có thể giúp người khác viết các kiểm tra cho bộ điều khiển của họ:

Bạn có tùy chọn không sử dụng xác nhận được cung cấp bởi System.ComponentModel.DataAnnotations nhưng vẫn sử dụng đối tượng ViewData.ModelState, bằng cách sử dụng nó AddModelError và một số cơ chế xác nhận khác. Ví dụ:

public ActionResult Create(CompetitionEntry competitionEntry)
{        
    if (competitionEntry.Email == null)
        ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");

    if (ModelState.IsValid)
    {
       // insert code to save data here...
       // ...

       return Redirect("/");
    }
    else
    {
        // return with errors
        var viewModel = new CompetitionEntryViewModel();
        // insert code to populate viewmodel here ...
        // ...


        return View(viewModel);
    }
}

Điều này vẫn cho phép bạn tận dụng lợi thế của Html.ValidationMessageFor() những thứ mà MVC tạo ra, mà không cần sử dụng DataAnnotations. Bạn phải đảm bảo khóa bạn sử dụng với AddModelError khớp với những gì mà khung nhìn mong đợi cho các thông báo xác thực.

Bộ điều khiển sau đó sẽ trở thành testable vì việc xác nhận đang diễn ra một cách rõ ràng, thay vì được thực hiện tự động bởi khung công tác MVC.


1
2017-09-23 19:52



Việc xác thực bằng cách này sẽ loại bỏ một số phần xác nhận hợp lệ nhất trong MVC. Tôi muốn thêm xác nhận vào mô hình của tôi, không phải trong bộ điều khiển. Nếu tôi sử dụng giải pháp này tôi sẽ kết thúc với rất nhiều bản sao mã có thể với những cơn ác mộng kèm theo. - Willem Meints
@ W.Meints: đúng, nhưng các dòng mã trong ví dụ trên làm xác thực cũng có thể được chuyển sang một phương thức trên Mô hình nếu bạn thích. Vấn đề là, việc xác thực thông qua mã thay vì Thuộc tính làm cho nó có thể kiểm chứng được hơn. Paul giải thích nó tốt hơn ở trên stackoverflow.com/a/1269960/22194 - codeulike


Tôi đồng ý rằng ARM có câu trả lời tốt nhất: kiểm tra hành vi của bộ điều khiển của bạn, không phải xác thực được tích hợp sẵn.

Tuy nhiên, bạn cũng có thể kiểm tra đơn vị rằng Model / ViewModel của bạn có các thuộc tính xác thực chính xác được xác định. Giả sử ViewModel của bạn trông như thế này:

public class PersonViewModel
{
    [Required]
    public string FirstName { get; set; }
}

Bài kiểm tra đơn vị này sẽ kiểm tra sự tồn tại của [Required] thuộc tính:

[TestMethod]
public void FirstName_should_be_required()
{
    var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");

    var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
                                .FirstOrDefault();

    Assert.IsNotNull(attribute);
}

1
2018-05-26 16:41



Làm thế nào chúng ta sẽ kiểm tra xác nhận tích hợp sau đó? Đặc biệt nếu chúng ta tùy chỉnh nó với các thuộc tính phụ, thông báo lỗi, v.v. - Teoman shipahi