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类是一个抽象类。
我被卡在这里了。
更新
完整的测试代码如下:
SRC:
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)
提示:我还使用了services.AddSingleton<IObjectValidator>(Mock.Of<IObjectValidator>())
模拟了IObjectValidator
services.AddTransient<IModelBinderFactory, SOMETHING>();
吗? - SteinGateservices.AddSingleton<IModelBinderFactory>(Mock.Of<IModelBinderFactory>());
- Nkosi