title: How to unit test method with TryUpdateModelAsync invovled in ASP.NET CORE Razor Page Web App?
body: <p>TryUpdateModelAsync is an internal protected method of abstract class PageModel, which makes it, to my view, unmockable. Is there any approach to unit test any of the action that involves this method? </p>
  
  <p>I saw this solution on the internet: 
  <a href="https://stackoverflow.com/questions/39259025/asp-net-core-mvc-controller-unit-testing-when-using-tryupdatemodel">asp.net core mvc controller unit testing when using TryUpdateModel</a>.</p>
  
  <p>However, it only applies to ASP.NET Core MVC Web App since TryUpdateModelAsync is a public method, so we can add an adapator to wrap this method and make our adaptor call the actual method. In Razor Page, this TryUpdateModelAsync is inaccessible from outside.</p>
  
  <p>The problem I was having right now is when I was unit testing the OnPostAsync method, <code>TryUpdateModelAsync</code> always throw an exception. I could never pass through that line to check the code logic after it.</p>
  
  <p>So here is the action I want to unit test:</p>
  
  <pre class="lang-cs prettyprint-override"><code> public class CreateModel : PageModel
      {
       //elided
          [BindProperty]
          public Post Post { get; set; }
  
          public async Task&lt;IActionResult&gt; OnPostAsync()
          {
              if (!ModelState.IsValid)
              {
                  return Page();
              }
              if (await TryUpdateModelAsync&lt;Post&gt;(Post, "Post", p =&gt; p.Title, p =&gt; p.SubTitle, p =&gt; p.Author, p =&gt; p.Content, p =&gt; p.CategoryID)){
                  //do stuff
                  return RedirectToPage("./Index");
              }
              //do stuff
              return Page();
          }
  </code></pre>
  
  <p>Here's the unit test I wrote for it:</p>
  
  <pre class="lang-cs prettyprint-override"><code>  [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
          }
  </code></pre>
  
  <p><strong>UPDATE</strong>:</p>
  
  <p>Here's the full initialization process I did for this test:</p>
  
  <pre class="lang-cs prettyprint-override"><code>  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&lt;ITempDataProvider&gt;());
              _pageContext = new PageContext(_actionContext){
                  ViewData = _viewData
              };
              _urlHelper = new UrlHelper(_actionContext);
          }
  </code></pre>
  
  <p>Then I got following exception message:</p>
  
  <pre><code>  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)
  </code></pre>
  
  <p>Then I changed my initialization to the following:</p>
  
  <pre class="lang-cs prettyprint-override"><code>  public void InitPostModelProperties(){
    var services = new ServiceCollection();
    services.AddTransient&lt;IModelMetadataProvider, ModelMetadataProvider&gt;();
   _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&lt;ITempDataProvider&gt;());
              _pageContext = new PageContext(_actionContext){
                  ViewData = _viewData
              };
              _urlHelper = new UrlHelper(_actionContext);
          }
  </code></pre>
  
  <p>Then I got new exception message:</p>
  
  <pre><code>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)
  </code></pre>
  
  <p>I followed the exception message and added these lines:</p>
  
  <pre class="lang-cs prettyprint-override"><code>services.AddSingleton&lt;IModelMetadataProvider, EmptyModelMetadataProvider&gt;();
  </code></pre>
  
  <p>Got new exception message:</p>
  
  <pre><code>  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)
  </code></pre>
  
  <p>I kept adding new lines:</p>
  
  <pre class="lang-cs prettyprint-override"><code> services.AddSingleton&lt;IModelBinderFactory,ModelBinderFactory&gt;();
  </code></pre>
  
  <p>Only to find the service can't be instansiated beause ModelBinderFactory class is an abstract class</p>
  
  <p>I am stuck here.</p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>The whole test code is shown below:</p>
  
  <p><strong>SRC</strong>:</p>
  
  <pre class="lang-cs prettyprint-override"><code>  public class BaseTests
      {
          private  HugoBlogContext _context;
  
          public HugoBlogContext Context =&gt; _context;
          public List&lt;Category&gt; CatList;
          public List&lt;Post&gt; PostList;
  
          public List&lt;Tag&gt; TagList;
  
          public List&lt;PostTag&gt; 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&lt;IModelMetadataProvider, EmptyModelMetadataProvider&gt;();
              services.AddSingleton&lt;IModelBinderFactory,ModelBinderFactory&gt;();
              _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&lt;ITempDataProvider&gt;());
              _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&lt;RedirectToPageResult&gt;();
              Context.Posts.Where(c =&gt; c.CategoryID == (CatList.Count + 1)).Should().NotBeNull();
          }
      }
  </code></pre>
  
  <p><strong>UPDATE</strong>
  After mocking, I got the following Exception Message:</p>
  
  <pre><code>  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)
  </code></pre>
  
  <p>FYI: I also mocked <code>IObjectValidator</code> by using <code>services.AddSingleton&lt;IObjectValidator&gt;(Mock.Of&lt;IObjectValidator&gt;())</code></p>