1

In my ViewModel, portions of functionality are enabled/disabled depending on the logged-in individual's permissions. The ViewModel relies on a dependency-injected ISecurity object to check if a user has a specific permission. Different portions of functionality require different permissions.

public Interface ISecurity
{
   bool UserHasPermision(int userId, string permission);
}

In my production code, the concrete implementation of ISecurity interacts with an external application which does not allow me to change an individual's permissions. I created a FakeSecurity class that would allow me to do this in unit tests.

class FakeSecurity: ISecurity
{
     private Dictionary<int, List<string>> permissions = new Dictionary<int, List<string>>();

     public bool UserHasPermission(int userId, string permission)
     {
         return permissions.ContainsKey(userId) && 
                permissions[userId].Contains(permission);
     }

     //Not defined in ISecurity
     public void SetPermission(int userId, string permission, bool hasPermission)
     {
         if (!permissions.ContainsKey(userId))
         {
              permissions[userId] = new List<string>();
         }
         List<string> userPermissions = permissions[userId];
         if (hasPermission) 
         {
              userPermissions.Add(permission);
         }
         else 
         {
              userPermissions.Remove(permission);
         }
     }
}

The problem here is that SetPermission() is not defined in the ISecurity interface, so in order for my Unit Tests to set an individual's permissions I need to cast the ISecurity object registered with my IUnityContainer to a FakeSecurity object. I am told that my unit test should be ignorant of the specific type of implementation that is being used for a particular interface and that calling methods that are not defined in the interface is an anti-pattern.

[TestMethod]
public void UserDoesNotHavePermission()
{
   // test setup
   IUnityContainer iocContainer = GetIocContainer();
   ISecurity sec = iocContainer.Resolve<ISecurity>(); //registered singleton
   (sec as FakeSecurity).SetPermission(GetCurrentUser().Id, "Save Colors", false);
   var viewModel = iocContainer.Resolve<MaintainColorsViewModel>(); //per-request

   // asserts
   Assert.IsFalse(viewModel.CanSave);
}

[TestMethod]
public void UserHasPermission()
{
   // test setup
   IUnityContainer iocContainer = GetIocContainer();
   ISecurity sec = iocContainer.Resolve<ISecurity>(); //registered singleton
   (sec as FakeSecurity).SetPermission(GetCurrentUser().Id, "Save Colors", true);
   var viewModel = iocContainer.Resolve<MaintainColorsViewModel>(); //per-request

   // asserts
   Assert.IsTrue(viewModel.CanSave);
}

Is this a bad practice or not? I realize that I shouldn't cast my ISecurity instace to a particular type within my application code, but is this really an issue Unit Tests?

NuclearProgrammer
  • 806
  • 1
  • 9
  • 19
  • I really don't think it's a problem in unit tests. – Ian P Nov 19 '15 at 19:18
  • Can you share some code of one of your viewmodels that interact with the `ISecurity` abstraction? Although not completely related to your question, seeing some examples of its usage would allow us to feedback on usage of the `ISecurity` in view models. – Steven Nov 19 '15 at 19:57

3 Answers3

3

I am told that my unit test should be ignorant of the specific type of implementation

This is incorrect. It is completely normal and good practice to let tests use both fake implementations and the class under test directly.

You however, are using the DI container in your unit tests, and that actually is bad practice. Although the use of the DI container is okay when you're writing integration tests (since you want to test components in integration with other components), using the DI library in unit tests leads to hard to read and maintain tests. With unit tests, you test code in isolation. This means that you usually create the class under test by hand, and inject the required fake dependencies to get the test running.

I would therefore expect such unit test to look like this:

public void CanSave_CurrentUserHasNoPermission_ReturnsFalse() {
    // Arrange
    var noPermission = new FakeSecurity { CurrentUserHasPermission = false };
    var viewModel = new MaintainColorsViewModel(noPermission);

    // Act
    bool actualResult = viewModel.CanSave;

    // Assert
    Assert.IsFalse(actualResult);
}

public void CanSave_CurrentUserHasPermission_ReturnsTrue() {
    // Arrange
    var hasPermission = new FakeSecurity { CurrentUserHasPermission = true };
    var viewModel = new MaintainColorsViewModel(hasPermission);

    // Act
    bool actualResult = viewModel.CanSave;

    // Assert
    Assert.IsTrue(actualResult);
}

public void CanSave_Always_QueriesTheSecurityForTheSaveColorsPermission() {
    // Arrange
    var security = new FakeSecurity();
    var viewModel = new MaintainColorsViewModel(security);

    // Act
    bool temp = viewModel.CanSave;

    // Assert
    Assert.IsTrue(security.RequestedPermissions.Contains("Save Colors"));
}    

There are a few things to note about this code:

  • Both the FakeSecurity and the MaintainColorsViewModel are created directly in the tests here; no DI library is used. This makes the tests much more readable and maintainable (and faster).
  • I considerably simplified the FakeSecurity class (shown below), because you want fake classes to be as simple as possible.
  • A third test is added to check explicitly whether the MaintainColorsViewModel requests the expected permission.
  • The AAA pattern (Arrange/Act/Assert) is implemented explicitly.

To allow these tests to be written the way they are, the following change has been made to the ISecurity abstraction:

interface ISecurity
{
    bool UserHasPermission(string permission);
}

The userId parameter has been removed from the UserHasPermission method. The reason for this is that the ISecurity implementation will be able to find out who the current user is by itself. Allowing consumers of ISecurity to pass this parameter along only means that the API is getting more complex, there is more code to write, there's a bigger chance of programming errors, and we therefore need more supporting tests. In other words, the sole addition of this userId property forces a lot of extra production and test code to write and maintain.

Here is the simpflified FakeSecurity class:

class FakeSecurity : ISecurity
{
    public bool CurrentUserHasPermission;
    public List<string> RequestedPermissions = new List<string>();

    public bool UserHasPermission(string permission)
    {
        this.RequestedPermissions.Add(permission);
        return this.CurrentUserHasPermission;
    }
}

The FakeSecurity class now has very little code and that makes it, just by looking at it, very easy to check for correctness. Remember, test code should be as simple as possible. Side note: replacing this class with a generated mock object, doesn't make our code easier. In most cases it will actually make our unit tests harder to read, understand and maintain.

One reason for developers to start using a DI container inside their unit tests is because the manual creation of the class under test (with all its fake dependencies) causes maintenance issues in their tests. This is true actually; if the MaintainColorsViewModel has multiple dependencies, and we would create that MaintainColorsViewModel in each test, the addition of a single dependency would cause us to change all our MaintainColorsViewModel tests. This often is a reason for developers to either use a DI container -or- revert to mocking frameworks.

This however is not a good reason to start using a DI container or mocking library. A simple refactoring can completely remove the maintenance problem; we just have to create a factory method as follows:

private static MaintainColorsViewModel CreateViewModel(params object[] dependencies) {
    return new MaintainColorsViewModel(
        dependencies.OfType<ISecurity>().SingleOrDefault() ?? new FakeSecurity(),
        dependencies.OfType<ILogger>().SingleOrDefault() ?? new FakeLogger(),
        dependencies.OfType<ITimeProvider>().SingleOrDefault() ?? new FakeTimeProvider(),
        dependencies.OfType<IUserContext>().SingleOrDefault() ?? new FakeUserContext());
}

Here I assume that the MaintainColorsViewModel contains 4 dependencies (namely ISecurity, ILogger, ITimeProvider and IUserContext). The CreateViewModel factory method allows passing in all dependencies using a params array, and the method tries to get each abstraction from the array and when missing replaces it with the default fake implementation.

With this factory, we can now rewrite our tests to the following:

[TestMethod]
public void CanSave_CurrentUserHasNoPermission_ReturnsFalse()
{
    // Arrange
    var noPermission = new FakeSecurity { CurrentUserHasPermission = false };

    MaintainColorsViewModel viewModel = CreateViewModel(noPermission);

    // Act
    bool actualResult = viewModel.CanSave;

    // Assert
    Assert.IsFalse(actualResult);
}

Or we can pass in multiple dependencies if the test requires this:

[TestMethod]
public void CanSave_CurrentUserHasNoPermission_LogsWarning()
{
    // Arrange
    var logger = new FakeLogger();
    var noPermission = new FakeSecurity { CurrentUserHasPermission = false };

    MaintainColorsViewModel viewModel = CreateViewModel(logger, noPermission);

    // Act
    bool temp = viewModel.CanSave;

    // Assert
    Assert.IsTrue(logger.Entries.Any());
}

Do note that this test is just here for educational purposes. I don't suggest the view model to actually do the logging; that should not be its responsibility.

The moral of the story here is actually that good design can simplify your testing efforts considerably to the point that you can write less code and less tests, while improving the quality of your software.

Steven
  • 166,672
  • 24
  • 332
  • 435
1

You shouldn't use a DI container in unit tests, see the answer in this question.

In unit tests, the object graph that you are testing is usually small (usually a single class). So you don't need a DI container.

Without a container, here is how your test would look like:

//Arrange
FakeSecurity fake_security = new FakeSecurity();

fake_security.SetPermission(GetCurrentUser().Id, "Save Colors", false);

MaintainColorsViewModel sut = new MaintainColorsViewModel(fake_security);

//Act
...

Please note that I am assuming that you are using constructor injection to inject ISecurity into MaintainColorsViewModel.

Please note that instead of creating a FakeSecurity class, you can use auto-generated mocks by using mocking frameworks. Here is a link to one of the mocking frameworks called FakeItEasy.

Community
  • 1
  • 1
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • I obviously agree with not using the DI container, but I'm often very reticent in advicing a mocking framework, because improving both the application's design and quality of the unit tests often removes the need to use such framework. – Steven Nov 19 '15 at 21:49
  • @Steven, wouldn't you agree that in this particular case, uses a mocking framework to create a stub for `ISecurity` is better than creating the `FakeSecurity` class? – Yacoub Massad Nov 19 '15 at 22:12
  • Even in this case I would not find a mocking framework useful. – Steven Nov 19 '15 at 22:16
  • @Steven, so you would create and use a `FakeSecurity` class instead? – Yacoub Massad Nov 19 '15 at 22:18
  • See my answer. I don't think you can make those tests in my answer more readable and maintainable by using a mocking library, but please prove me wrong. – Steven Nov 19 '15 at 22:34
  • There is the definition of the `FakeSecurity`. That is extra code that wouldn't exist if you use a mocking framework. – Yacoub Massad Nov 19 '15 at 23:19
  • But you replace that code with the setup of your mocking framework. Again, I would like you to show me an example (of my tests) where you show how this looks in say FakeItEasy. Would you be so kind to update your question with such example? – Steven Nov 20 '15 at 05:45
  • @Steven, [here it is](http://pastie.org/10569536). And when I see that I have a lot of tests that require a similar stub, I create a helper method [like this](http://pastie.org/10569558). – Yacoub Massad Nov 20 '15 at 11:19
  • I must admit that your code example *is* pretty clean, but certainly not *cleaner* than the tests I defined in my answer. And do note that your helper method is about the same size as the fake class I defined so (with the assumption that you have SOLID code) you're not improving maintainability, readability and I would even say that you don't even improve your productivity. – Steven Nov 20 '15 at 11:57
  • @Steven, what about more complex stubs? If different tests want the stub to behave differently, would you create multiple fake classes in this case? – Yacoub Massad Nov 21 '15 at 00:45
  • Sometimes I create multiple fake implementations, but up untill now, usually one or two fakes are enough in the systems I built and maintained. I'm not saying that mocking frameworks are never useful, and it is dependent on what amd how you're testing, but I didn't need them (yet). Funny actually that advocate against containers, but in favor of mocking frameworks, while I do it the other way around, don't you think :) – Steven Nov 21 '15 at 06:00
0

Based on my experience, when you feel something not natural in Unit Test, you may want to re-factor your code.

According to this code, there are a couple choices.

  1. Define the permission dictionary as a property in the interface. So it is easy to set values at unit testing.
  2. Define a permission layer, IPermission, to add/retrieve/remove permission. Then you can mock IPermission in unit testing your Security implementation.
  3. At least I would define the SetPermission method in ISecurity, your current code does not let you define an ISecurity object and let you set permission. Think the following code.

    { ISecurity sec = CreateSecurity() sec.SetPermission() // ERROR, SetPermission is not a method in ISecurity. }

    private ISecurity CreateSecurity() { return new Security() }

    However, I am not sure how to unit test in this case on top of my head.