jeudi 19 novembre 2015

Using Methods not Defined in Interface on Fakes within Unit Tests

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?

Aucun commentaire:

Enregistrer un commentaire