I am introducing automatic testing using NUnit, NSubstitute for a project which uses Ninject and generic repositories.
For regression testing, I am replacing generic repositories with in memory ones to prevent working with a database.
Also, to test security constraints of services, I am mocking the security service which looks like this:
public class SecurityService : ISecurityService
{
#region Properties
private IScopedDataAccess DataAccess { get; }
private IMappingService MappingService { get; }
#endregion
#region Constructor
public SecurityService(IScopedDataAccess scopedDataAccess, IMappingService mappingService)
{
DataAccess = scopedDataAccess;
MappingService = mappingService;
}
#endregion
#region Methods
public virtual string GetUsername()
{
return HttpContext.Current.User.Identity.Name;
}
public AppUserSecurityProfileServiceModel GetCurrentUserData()
{
var username = GetUsername();
var userDataModel = DataAccess.AppUserRepository.AllNoTracking.FirstOrDefault(u => u.Username == username);
if (userDataModel == null)
return null;
var ret = MappingService.Mapper.Map<AppUserSecurityProfileServiceModel>(userDataModel);
return ret;
}
public virtual int GetCurrentUserId()
{
var userData = GetCurrentUserData();
if (userData == null)
throw new SecurityException($"No user data could be fetched for - {GetUsername()}");
return userData.AppUserId;
}
public bool IsInRole(UserRoleEnum role, int? userId = null)
{
int actualUserId = userId ?? GetCurrentUserId();
var hasRole = DataAccess.AppUserXUserRoleRepository.AllNoTracking.Any(x => x.AppUserId == actualUserId && x.UserRoleId == (int) role);
return hasRole;
}
public bool CanPerformAction(UserActionEnum action, int? userId = null)
{
int actualUserId = userId ?? GetCurrentUserId();
var hasAction = DataAccess.AppUserXUserRoleRepository.AllNoTracking
.Where(x => x.AppUserId == actualUserId)
.Join(DataAccess.UserRoleRepository.AllNoTracking, xRole => xRole.UserRoleId, role => role.UserRoleId, (xRole, role) => role)
.Join(DataAccess.UserRoleXUserActionRepository.AllNoTracking, xRole => xRole.UserRoleId, xAction => xAction.UserRoleId,
(role, xAction) => xAction.UserActionId)
.Contains((int) action);
return hasAction;
}
// other methods can appear here in the future
#endregion
}
Each regression test fakes current user like this:
public void FakeCurrentUser(int userId)
{
var userRef = DataAccess.AppUserRepository.AllNoTracking.FirstOrDefault(u => u.AppUserId == userId);
var securitySubstitude = Substitute.ForPartsOf<SecurityService>(Kernel.Get<IScopedDataAccess>(), Kernel.Get<IMappingService>());
securitySubstitude.When(x => x.GetUsername()).DoNotCallBase();
securitySubstitude.GetUsername().Returns(userRef?.Username ?? "<none>");
securitySubstitude.When(x => x.GetCurrentUserId()).DoNotCallBase();
securitySubstitude.GetCurrentUserId().Returns(userId);
Kernel.Rebind<ISecurityService>().ToConstant(securitySubstitude);
}
Basically, it takes care to replace methods that are based on the context (i.e. HttpContext
in my case), but leaves other method intact.
Each tested service will be instantiated after this initialization, so I am sure that the appropriate instance is injected.
Question: is it OK to mock the service like this or is it an anti-pattern?