En ocasiones he necesitado modificar el comportamiento de una propiedad privada de una clase, por ejemplo porque esa propiedad sea una clase de un repositorio en una arquitectura basada en Unit of Work y Repository Pattern como la que comento en esta otra entrada.
Esto es muy fácil de hacer con librerías de testing como pueda ser Rhino Mocks.
Posible situación práctica
Supongamos que tenemos la siguiente interfaz de nuestra unidad de trabajo:
1 2 3 4 5 6 7 8 |
public interface IContext { IRepository<Post> PostRepository { get; } IRepository<Comment> CommentRepository { get; } int Save(); } |
Esas interfaces de IRepository podrían ser las siguientes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public interface IRepository<TEntity> { IEnumerable<TEntity> Get( Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null ); TEntity GetById(object id); IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate); TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate); TEntity First(Expression<Func<TEntity, bool>> predicate); void Delete(object id); void Delete(TEntity entityToDelete); void Add(TEntity entityToAdd); void Attach(TEntity entityToAttach); void Update(TEntity entityToUpdate); IEnumerable<TEntity> GetPagedElements<TKey>(int pageIndex, int pageCount, Expression<Func<TEntity, TKey>> orderByExpression, bool ascending = true); } |
La implementación de la unidad de trabajo sería algo así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
public class UnitOfWork : DbContext, IUnitOfWork { private Repository<Post> postRepository; private Repository<Comment> commentRepository; public UnitOfWork() : base("connectionString") { this.Initialize(); } public UnitOfWork(string connectionString) : base(connectionString) { this.Initialize(); } public void Initialize() { this.postRepository = new RepositoryPost(Posts, (DbContext)base.MemberwiseClone()); this.commentRepository = new Repository<Comment>(Comments, (DbContext)base.MemberwiseClone()); } public IDbSet<Post> Posts { get; set; } public IDbSet<Comment> Comments { get; set; } public IRepositoryPost PostRepository { get { return this.postRepository; } } public IRepository<Comment> CommentRepository { get { return this.commentRepository; } } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Configurations.Add(new PostMap()); modelBuilder.Configurations.Add(new CommentMap()); } public int Save() { int saveChangesResult; try { saveChangesResult = this.SaveChanges(); } catch(exception e) { saveChangesResult = 0; } return saveChangesResult; } } |
Para acceder a la propiedad privada postRepository tenemos una propiedad pública PostRepository de solo lectura que devolverá el postRepository.
Por lo tanto cuando desde un método que trabaja con nuestra nuestra unidad de trabajo IContext queramos por ejemplo borrar un post deberemos aplicar lo siguiente:
1 |
context.PostRepository.Delete(id); |
Esta es la línea a la que queremos aplicar un stub en el unit test del método que llama a nuestra unidad de trabajo para borrar un post.
Solución
Esto lo podemos hacer de la siguiente manera:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[TestFixture] [ExcludeFromCodeCoverage] public class MyDomainServiceTest { private IContext context; private IRepository<Post> postRepository; [SetUp] public void Setup(){ postRepository = MockRepository.GenerateMock<IRepository<Post>>(); context = MockRepository.GenerateMock<IContext>(); } } |
De esta forma ya tenemos “context” y “postRepository” listos para ser usados en los test.
En relación a la línea de código donde se usa el Delete, lo único que habría que hacer en el test antes de hacer la llamada al método que contiene dicha línea, es lo siguiente:
1 2 |
postRepository.Stub(x => x.Delete(Arg<string>.Is.Anything)); context.Stub(x => x.PostRepository).Return(postRepository); |
De esta forma estamos usando un stub del método Delete cuando llamemos al repositorio postRepository que hemos creado con MockRepository.GenerateMock<IRepository<Post>>(), y por otro lado cuando usemos la propiedad PostRepository de context, estaremos devolviendo el repositorio creado anteriormente con el stub configurado.
Pues esto es todo para esta entrada, próximas entradas continuaré indicando como resolver aspectos relacionados con el unit testing.
Un saludo.