mercredi 31 août 2016

asp.net core mvc controller unit testing when using TryUpdateModel

In an asp.net core application, I have a pair of controller methods that respond to an Edit action. One for Get, which takes a string parameter for the entity id:

public async Task<IActionResult> Edit(string id)

and the other for receiving a post of updated entity values:

    [HttpPost]
    [ActionName("Edit")]
    [ValidateAntiForgeryToken]        
    public async Task<IActionResult> EditSave(string id)

Inside the postable action method, I call

var bindingSuccess = await TryUpdateModelAsync(vm);

And that works fine.

Now, I'm trying to write a test for this, but am finding that TryUpdateModelAsync requires lots of things from HttpContext and the controller to be fleshed out. I've tried mocking those out, but after looking at the source code for TryUpdateModelAsync, I realize that I'd essentially need to mock everything down to the metadata, which isn't proving to be straightforward.

I'm wondering if perhaps this difficulty is telling me something: TryUpdateModelAsync makes it hard to test, so I should refactor the controller method to not rely on this helper. Instead, I could add another parameter to the method for my viewmodel and decorate it with [FromBody], so the model binding would happen from the post fields when present, but I would be able to pass in a view model when testing. However, I like the TryUpdateModelAsync method, because it does the busy work of merging the post fields into my view model. The other way I can think to accomplish the merging is write my own Merge method. Okay, no big deal, but I'd prefer not to have to do this for each entity (or reinvent the wheel writing a reflection based merger) and really, I'm wondering if I just missed the boat on how to write a unit test against this. I could fire up a whole TestServer, like I do for integration tests, but I'm not sure this is the right direction and feels like I would just be complicating my unit tests further. However, maybe it is justified in this scenario?

I've seen answers that worked with previous versions of .net mvc, where all they needed to do was mock an IValueProvider and attach it to the controller, but in .net core it appears the TryUpdateModelAsync was reworked and requires more moving parts.

In sum, I see three options:

  1. Continue mocking and stubbing out all the pieces that TryUpdateModelAsync needs. This might be a dead end, it seems to be so far.
  2. Use TestServer and make this test from a little higher altitude using an HttpClient
  3. Refactor this method to use [FromBody] on a view model parameter, then write my own merge methods for each entity, there by side stepping TryUpdateModelAsync altogether

These all have their drawbacks, so I'm hoping there's a 4th entry to this list that I don't see because I am ignorant.

Aucun commentaire:

Enregistrer un commentaire