2017-07-10 1 views
1

J'ai du code (C# .Net Core WebAPI) Je souhaite faire un test unitaire mais j'ai besoin d'aide car les dépendances me semblent un peu bizarres.IOption pattern - tests unitaires et passage de couches

Le code est venu de quelques exemples de code (j'ai trouvé sur le web) pour accéder à l'aide MongoDB .Net de base WebAPI, qui, initialement, avait l'air correct, jusqu'à présent ..

Les deux DbContext et le dépôt ont la même dépendance - et le dépôt passe juste à travers la DbContext de toute façon - comme le dépôt instancie le DbContext:

public class LogItemRepository : ILogItemRepository 
    { 
    private readonly DbContext _context = null; 

    public LogItemRepository(IOptions<DbSettings> settings) 
    { 
     _context = new DbContext(settings); 
    } 

...

public class DbContext 
    { 
    private readonly IMongoDatabase _database = null; 

    public DbContext(IOptions<DbSettings> settings) 
    { 
     var client = new MongoClient(settings.Value.ConnectionString); 
     if (client != null) 
     _database = client.GetDatabase(settings.Value.Database); 
    } 

    public IMongoCollection<LogItem> LogItemsCollection 
    { 
     get 
     { 
     return _database.GetCollection<LogItem>("LogItem"); 
     } 
    } 
    } 
} 

Je ne suis pas familier avec le Options pattern, mais d'une lecture rapide, il semble bon. Mais je ne suis pas convaincu que c'est une bonne pratique de faire des dépendances d'enfants (les options), des dépendances du parent (comme dans l'exemple ci-dessus).

Au lieu de cela, devrais-je créer une interface, IDbContext, et l'utiliser comme dépendance pour le référentiel? C'est ce que j'aurais fait par le passé - mais je ne sais pas si cela casse le schéma des options.

Je soupçonne que c'est subjectif, mais je voudrais d'autres entrées.

Merci Tim

+2

Bien que principalement basée sur l'opinion, la pratique courante consiste à ne pas instancier le contexte db dans le constructeur du référentiel. Cela couple étroitement le référentiel au contexte. Injectez une abstraction comme vous l'avez indiqué dans votre OP. Seul le contexte semble s'appuyer sur l'abstraction des options – Nkosi

+0

+1 sur le commentaire de Nkosi. Le fait que le référentiel instancie un contexte conduira à toutes sortes de problèmes sur la route. 5 référentiels utiliseraient potentiellement 5 contextes, ce qui signifierait que les références aux entités transmises/retournées seraient hors de la portée de leur contexte ou que tout un tas de câblages désordonnés détacheraient/rattacheraient les entités entre les contextes.En ce qui concerne les tests unitaires (comme dans les tests TDD/BDD), coupez votre moquerie au niveau du Repository, comme pour simuler le dépôt plutôt que d'essayer de se moquer du niveau du contexte. Les tests d'intégration avec un db peuvent tester les dépôts. –

Répondre

1

Tout d'abord fondée sur des opinions, la pratique courante est de ne pas instancier le contexte db dans le constructeur du référentiel. Cela couple étroitement le référentiel au contexte. Injectez une abstraction comme vous l'avez indiqué dans votre OP.

Il se peut que je froisse des poils ici, mais il y a toujours trop de couplage serré dans l'exemple fourni.

D'abord le contexte abstrait

public interface IDbContext { 
    IMongoCollection<LogItem> LogItemsCollection { get; } 
} 

et ont également IMongoDatabase être une dépendance explicite

public class DbContext : IDbContext { 
    private readonly IMongoDatabase database = null; 

    public DbContext(IMongoDatabase database) 
     this.database = database; 
    } 

    public IMongoCollection<LogItem> LogItemsCollection { 
     get { 
      return database.GetCollection<LogItem>("LogItem"); 
     } 
    } 
} 

service configure à ce que jamais les options sont nécessaires à la racine de la composition (de démarrage). Vous pourriez même envisager de l'encapsuler dans une méthode d'extension.

services.AddScoped<IMongoDatabase>(provider => { 
    var settings = provider.GetService<IOptions<DbSettings>>(); 
    var client = new MongoClient(settings.Value.ConnectionString); 
    return client.GetDatabase(settings.Value.Database); 
}); 
services.AddScoped<IDbContext, DbContext>(); 
services.AddScoped<ILogItemRepository, LogItemRepository>(); 
//...NOTE: Use the desired service lifetime. This is just an example 

Cela laisse maintenant le dépôt d'être explicitement dépendant de l'abstraction contexte

public class LogItemRepository : ILogItemRepository { 
    private readonly IDbContext context = null; 

    public LogItemRepository(IDbContext context) { 
     this.context = context; 
    } 

    //...other code 
} 

Toutes les couches sont désormais découplées et explicitement ce que leurs dépendances sont, ce qui permet des tests unitaires plus isolés à faire comme nécessaire.

+0

Merci @Nkosi - ça me va mieux. Va aller avec cette approche. –