3

I have a basic CRUD application written in ASP.NET Core, with an example controller action looking like so:

public IActionResult Sheet(Guid characterId)
{
    var character = _repository.GetCharacter(characterId);
    // omit validation for brevity
    return View("Sheet", new ViewModel(character, true, true));
}

And an example unit test:

[Fact]
public void TestSheet()
{
    // Arrange stuff and mock setup
    // Act
    var result = _controller.Sheet(characterId) as ViewResult;

    // Assert
    Assert.NotNull(result);

    // assert ViewModel constructor called with x arguments
}

Other research seems to suggest having a mock of the object, but that would not be appropriate for this scenario. I could simply have the constructor for ViewModel set a couple of getter properties, but that would seem to be delving into the realm of testing ViewModel as opposed to the controller. The controller should not care about what the ViewModel does with the values that are passed to it, and in my opinion neither should the test - the test is testing that the controller behaves properly, and should really only test that it is passing the correct values. A couple of getter properties would certainly achieve this but it just seems conceptually wrong.

Telvee32
  • 109
  • 1
  • 7

1 Answers1

0

The action is tightly coupled to the creation of the view model and this can be inverted

public interface IViewModelFactory {
    T CreateNew<T>(params object[] args) where T : ViewModelBase;
}

public class ViewModelFactory : IViewModelFactory {
    public T CreateNew<T>(params object[] args) where T : ViewModelBase {
        return (T)Activator.CreateInstance(typeof(T), args);
    }
}

public class ViewModelBase { }

so that it is not the responsibility of the controller to create/initialize view models. This will be an explicit dependency injected into the controller and used to create the view models.

public IActionResult Sheet(Guid characterId) {
    var character = _repository.GetCharacter(characterId);
    // omit validation for brevity
    return View("Sheet", _viewModelFactory.CreateNew<ViewModel>(character, true, true));
}

And desired behavior can be asserted when exercised in unit tests

[Fact]
public void TestSheet() {
    // Arrange stuff and mock setup
    var factory = new Mock<IViewModelFactory>();
    factory.Setup(_ => _.CreateNew<ViewModel>(It.IsAny<object[]>()))
           .Returns((object[] args) =>
               new ViewModel((Character)args[0], (bool)args[1], (bool)args[2])
           );      

    // Act
    var result = _controller.Sheet(characterId) as ViewResult;

    // Assert
    Assert.NotNull(result);

    // assert ViewModel constructor called with x arguments
    factory.Verify(_ => _.CreateNew<ViewModel>(It.IsAny<Character>(), true, true), Times.AtLeastOnce());
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472