0

Ninjectと汎用リポジトリを使用するプロジェクトでは、NSnitを使用して自動テストを導入しています。セキュリティを部分的に嘲笑するのは良い方法ですか?

回帰テストでは、汎用リポジトリをメモリに置き換えてデータベースとの連携を防ぎます。

また、サービスのセキュリティ制約をテストするために、私はこのようになりますセキュリティサービスからかっています:、基本的にそれを

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); 
} 

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 
} 

を各回帰テストは、このような現在のユーザーを偽装コンテキストに基づいたメソッド(私の場合はHttpContext)を置き換えるように注意しますが、他のメソッドはそのままです。

このテスト済みの各サービスはこの初期化後にインスタンス化されるため、適切なインスタンスが挿入されていることを確認します。

質問:このようなサービスを模擬するのは問題ないのですか、それともアンチパターンですか?

答えて

1

あなたはこのアプローチに特に関心がありますか?この場合、実行可能なようです。

個人的に私はパーツが実際であるか、実際のコードと呼ばれる部分が偽造されているかをより注意深く追跡しなければならないので、部分的なモックを避けたい。ここでコードを変更する柔軟性がある場合は、HttpContext関連のものを別の依存関係にプッシュして、それを代わりに偽造することができます。以下のような

何か:

public interface IUserInfo { 
    string GetUsername(); 
    int GetCurrentUserId(); 
} 

public class HttpContextUserInfo : IUserInfo { 
    public string GetUsername() { return HttpContext.Current.User.Identity.Name; } 
    public int GetCurrentUserId() { ... } 
} 

public class SecurityService : ISecurityService 
{ 
    private IScopedDataAccess DataAccess { get; } 
    private IMappingService MappingService { get; } 
    // New field: 
    private IUserInfo UserInfo { get; } 

    // Added ctor argument: 
    public SecurityService(IScopedDataAccess scopedDataAccess, IMappingService mappingService, IUserInfo userInfo) 
    { ... } 

    public AppUserSecurityProfileServiceModel GetCurrentUserData() 
    { 
     var username = UserInfo.GetUsername(); 
     var userDataModel = DataAccess.AppUserRepository.AllNoTracking.FirstOrDefault(u => u.Username == username); 
     ... 
     return ret; 
    } 

    public bool IsInRole(UserRoleEnum role, int? userId = null) 
    { 
     int actualUserId = userId ?? UserInfo.GetCurrentUserId(); 
     var hasRole = ...; 
     return hasRole; 
    } 

    public bool CanPerformAction(UserActionEnum action, int? userId = null) 
    { 
     int actualUserId = userId ?? UserInfo.GetCurrentUserId(); 
     var hasAction = ...; 
     return hasAction; 
    }  
} 

は今、あなたはあなたのテストのための IUserInfoの別の実装を渡すのは自由です(手で実装するか、あざけるライブラリを使用することができます)。これはテストされている SecurityServiceのすべてが実際のコードを呼び出していることを知っていて、テストの依存関係を操作してそのコードのさまざまな部分を実行することができるため、部分的なモックに関する私の最初の懸念に対処しています。コストの面では、もう一つのクラスが必要になります(仮想メソッドを使用して1つのクラスに固執する可能性があります)が、ソリューションの複雑さを少し増やします。

これが役に立ちます。

+0

はい、コードを変更できます。独立した依存関係を持つことは、コンテキストを生成しているものを正確に模倣するので、非常に良い考えです。セキュリティの依存関係にあるものはどれも「本当の」ものです。ありがとう。 – Alexei

関連する問題