繁体   English   中英

如何在 ASP.NET CORE Razor Page Web App 中使用 TryUpdateModelAsync 对方法进行单元测试?

[英]How to unit test method with TryUpdateModelAsync invovled in ASP.NET CORE Razor Page Web App?

TryUpdateModelAsync 是抽象类 PageModel 的内部受保护方法,在我看来,这使得它不可模仿。 是否有任何方法可以对涉及此方法的任何操作进行单元测试?

我在互联网上看到了这个解决方案: asp.net core mvc controller unit testing when using TryUpdateModel

但是,它仅适用于 ASP.NET Core MVC Web App,因为 TryUpdateModelAsync 是一个公共方法,因此我们可以添加一个适配器来包装此方法并使我们的适配器调用实际方法。 在 Razor Page 中,无法从外部访问此 TryUpdateModelAsync。

我现在遇到的问题是当我对 OnPostAsync 方法进行单元测试时, TryUpdateModelAsync总是抛出异常。 我永远无法通过那条线来检查它之后的代码逻辑。

所以这是我要单元测试的操作:

 public class CreateModel : PageModel
    {
     //elided
        [BindProperty]
        public Post Post { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            if (await TryUpdateModelAsync<Post>(Post, "Post", p => p.Title, p => p.SubTitle, p => p.Author, p => p.Content, p => p.CategoryID)){
                //do stuff
                return RedirectToPage("./Index");
            }
            //do stuff
            return Page();
        }

这是我为它编写的单元测试:

  [Test]
  public async Task InvokeOnPostAsyncWithInvalidModelState_ShouldReturnPageResultType(){
            //Arrange
            InitPostModelProperties();
            var createModel = new CreateModel(){
                PageContext = _pageContext,
                TempData = _tempData,
                Url = _urlHelper
            };
           //do arrangement

            //Act
            var result = await createModel.OnPostAsync();

            //Assert
        }

更新

这是我为此测试所做的完整初始化过程:

  public void InitPostModelProperties(){
 _httpContext = new DefaultHttpContext(){
                //RequestServices = services.BuildServiceProvider()
            };
            _modelStateDictionary = new ModelStateDictionary();
            _modelMetaDataProvider = new EmptyModelMetadataProvider();
            _actionContext = new ActionContext(_httpContext, new RouteData(), new PageActionDescriptor(), _modelStateDictionary);
            _viewData = new ViewDataDictionary(_modelMetaDataProvider, _modelStateDictionary);
            _tempData = new TempDataDictionary(_httpContext, Mock.Of<ITempDataProvider>());
            _pageContext = new PageContext(_actionContext){
                ViewData = _viewData
            };
            _urlHelper = new UrlHelper(_actionContext);
        }

然后我收到以下异常消息:

  Error Message:
   System.ArgumentNullException : Value cannot be null.
Parameter name: metadataProvider
  Stack Trace:
     at Microsoft.AspNetCore.Mvc.ModelBinding.Internal.ModelBindingHelper.TryUpdateModelAsync(Object model, Type modelType, String prefix, ActionContext actionContext, IModelMetadataProvider metadataProvider, IModelBinderFactory modelBinderFactory, IValueProvider valueProvider, IObjectModelValidator objectModelValidator, Func`2 propertyFilter)
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync[TModel](TModel model, String name, Expression`1[] includeExpressions)

然后我将初始化更改为以下内容:

  public void InitPostModelProperties(){
  var services = new ServiceCollection();
  services.AddTransient<IModelMetadataProvider, ModelMetadataProvider>();
 _httpContext = new DefaultHttpContext(){
                RequestServices = services.BuildServiceProvider()
            };
            _modelStateDictionary = new ModelStateDictionary();
            _modelMetaDataProvider = new EmptyModelMetadataProvider();
            _actionContext = new ActionContext(_httpContext, new RouteData(), new PageActionDescriptor(), _modelStateDictionary);
            _viewData = new ViewDataDictionary(_modelMetaDataProvider, _modelStateDictionary);
            _tempData = new TempDataDictionary(_httpContext, Mock.Of<ITempDataProvider>());
            _pageContext = new PageContext(_actionContext){
                ViewData = _viewData
            };
            _urlHelper = new UrlHelper(_actionContext);
        }

然后我收到了新的异常消息:

Outcome: Failed
    Error Message:
    System.InvalidOperationException : No service for type 'Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider' has been registered.
    Stack Trace:
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.get_MetadataProvider()
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync[TModel](TModel model, String name, Expression`1[] includeExpressions)

我按照异常消息并添加了以下几行:

services.AddSingleton<IModelMetadataProvider, EmptyModelMetadataProvider>();

收到新的异常消息:

  Outcome: Failed
    Error Message:
    System.InvalidOperationException : No service for type 'Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory' has been registered.
    Stack Trace:
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.get_ModelBinderFactory()
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync[TModel](TModel model, String name, Expression`1[] includeExpressions)

我不断添加新行:

 services.AddSingleton<IModelBinderFactory,ModelBinderFactory>();

才发现服务不能被实例化,因为 ModelBinderFactory 类是一个抽象类

我被困在这里。

更新

整个测试代码如下所示:

资源中心

  public class BaseTests
    {
        private  HugoBlogContext _context;

        public HugoBlogContext Context => _context;
        public List<Category> CatList;
        public List<Post> PostList;

        public List<Tag> TagList;

        public List<PostTag> PostTagList;

        public BaseTests(){}


 public class PageModelBaseTests: BaseTests
    {
        protected ICategoryRepository _categoryRepository;

        protected IPostRepository _postRepository;

        protected IPostTagRepository _postTagRepository;

        protected ITagRepository _tagRepository;

        protected ISelectTagService _selectTagService;

        protected IDropdownCategoryService _dropdownCategoryService;

        protected HttpContext _httpContext;

        protected ModelStateDictionary _modelStateDictionary;

        protected ActionContext _actionContext;

        protected ModelMetadataProvider _modelMetaDataProvider;

        protected ViewDataDictionary _viewData;

        protected TempDataDictionary _tempData;

        protected PageContext _pageContext;

        protected UrlHelper _urlHelper;



        [SetUp]
        public void Init(){
            _categoryRepository = new CategoryRepository(Context);
            _postRepository = new PostRepository(Context);
            _postTagRepository = new PostTagRepository(Context);
            _tagRepository = new TagRepository(Context);
            _selectTagService = new SelectTagService(_tagRepository, _postRepository, _postTagRepository);
            _dropdownCategoryService = new DropdownCategoryService(_categoryRepository);
        }


        public void InitPostModelProperties(){
            var services = new ServiceCollection();
            services.AddSingleton<IModelMetadataProvider, EmptyModelMetadataProvider>();
            services.AddSingleton<IModelBinderFactory,ModelBinderFactory>();
            _httpContext = new DefaultHttpContext(){
                RequestServices = services.BuildServiceProvider()
            };
            _modelStateDictionary = new ModelStateDictionary();
            _modelMetaDataProvider = new EmptyModelMetadataProvider();
            _actionContext = new ActionContext(_httpContext, new RouteData(), new PageActionDescriptor(), _modelStateDictionary);
            _viewData = new ViewDataDictionary(_modelMetaDataProvider, _modelStateDictionary);
            _tempData = new TempDataDictionary(_httpContext, Mock.Of<ITempDataProvider>());
            _pageContext = new PageContext(_actionContext){
                ViewData = _viewData
            };
            _urlHelper = new UrlHelper(_actionContext);
        }


[TestFixture]
  public class CreateModelTests: PageModelBaseTests
    {

[Test]
    public async Task InvokeOnPostAsyncWithValidModelState_ShouldReturnRedirectPageResultTypeAndCategoryShouldBeUpdated(){
              //Arrange
            InitPostModelProperties();
            var createModel = new CreateModel(_selectTagService, _dropdownCategoryService ,_postRepository){
                PageContext = _pageContext,
                TempData = _tempData,
                Url = _urlHelper,
            };
            createModel.Post = new Post{
                Title = "Create",
                SubTitle = "Create",
                Content = "Create",
                Author = "Create",
                CategoryID = CatList.Count + 1,
            };
            var selectedTags = new string[3]{"4","5","6"};

            //Act
            var result = await createModel.OnPostAsync(selectedTags);

            //Assert
            result.Should().BeOfType<RedirectToPageResult>();
            Context.Posts.Where(c => c.CategoryID == (CatList.Count + 1)).Should().NotBeNull();
        }
    }

更新嘲笑后,我收到以下异常消息:

  Outcome: Failed
    Error Message:
    System.NullReferenceException : Object reference not set to an instance of an object.
    Stack Trace:
       at Microsoft.AspNetCore.Mvc.ModelBinding.Internal.ModelBindingHelper.TryUpdateModelAsync(Object model, Type modelType, String prefix, ActionContext actionContext, IModelMetadataProvider metadataProvider, IModelBinderFactory modelBinderFactory, IValueProvider valueProvider, IObjectModelValidator objectModelValidator, Func`2 propertyFilter)
   at Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync[TModel](TModel model, String name, Expression`1[] includeExpressions)

供参考:我还嘲笑IObjectValidator通过使用services.AddSingleton<IObjectValidator>(Mock.Of<IObjectValidator>())

我不确定测试TryValidateModelTryUpdateModelAsync是否是我们的责任; 假设框架方法是正确的,肯定是安全的,对。

另一个论点是您正在测试您的实现而不是其他人的。

无论如何,如果你想忽略这些方法,你可以在你的测试方法中尝试这个(我使用Moq作为我的 Mocking 框架)完全忽略这些方法,实际上假设它们是正确的:

var objectModelValidatorMock = new Mock<IObjectModelValidator>();
objectModelValidatorMock
    .Setup(o => o.Validate(
        It.IsAny<ActionContext>(),
        It.IsAny<ValidationStateDictionary>(),
        It.IsAny<string>(),
        It.IsAny<object>()));

var sut = new NewController
{
    ObjectValidator = objectModelValidatorMock.Object
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM