I will pass a way of my testing. I do not know if it is the best, but it can be a starting point for us to produce something more concise and coherent.
The testing method and Mocks gypsy
It took me a long time to find something that was good enough for a Mock and never found it. The texts I find on this subject usually talk about everything and do not explain anything, so I decided to make some Mocks homemade that work well for my case.
I’m going to take a series of steps to produce a test model quickly.
Method 1: Not using a pre-existing base
This method I find more interesting from the point of view of the test because it does not count with a ready base and its vices.
Step 1: Extract the Interface from DbContext
and use IDbSet
instead of DbSet
Extracting the context interface is simple:
- In the class of your context, right-click on its name, select Refactor > Extract Interface;
- Visual Studio will suggest a name. Click OK to generate the interface and modify the context class to use its interface.
There’ll be something like that:
namespace MeuProjeto.Models
{
public class MeuProjetoContext : DbContext, MeuProjeto.Models.IMeuProjetoContext
{
...
}
}
Replace now all occurrences of DbSet
for IDbSet
. This causes the system and the test project to use the same context, but with implementations of DbSet
different.
I mean, mine looked something like this:
namespace MeuProjeto.Models
{
public class MeuProjetoContext : DbContext, MeuProjeto.Models.IMeuProjetoContext
{
...
public IDbSet<Colaborador> Colaboradores { get; set; }
public IDbSet<Login> Logins { get; set; }
...
}
}
Step 2: Create a Test Project with the directories Controllers
and Models
That part doesn’t have much of a secret:
Step 3: Create a Lie Context in Models
(in the test project)
I am assuming that here you have already added the reference of the main project in the test project (right click on Reference > Add Reference...).
Also install the Entity Framework in the test project. Just add the reference to System.Entity.Data
no use.
Mine was like this:
namespace MeuProjeto.Testes.Models
{
public class MeuProjetoFakeContext : DbContext, MeuProjeto.Models.IMeuProjetoContext
{
...
public IDbSet<Colaborador> Colaboradores { get; set; }
public IDbSet<Login> Logins { get; set; }
...
}
}
The advantage is that any and all update on the interface you do will make you update the interface of the lie context with just one click.
Step 4: Implement a FakeDbSet
generic
Mine was like this:
namespace MeuProjeto.Testes.Models
{
public class FakeDbSet<T> : IDbSet<T>
where T : class
{
HashSet<T> _dados;
IQueryable _query;
public FakeDbSet()
{
// Aqui não precisa ser HashSet. Pode ser uma Lista.
_dados = new HashSet<T>();
_query = _dados.AsQueryable();
}
public virtual T Find(params object[] keyValues)
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
public void Add(T item)
{
_dados.Add(item);
}
public void Remove(T item)
{
_dados.Remove(item);
}
public void Attach(T item)
{
_dados.Add(item);
}
public void Detach(T item)
{
_dados.Remove(item);
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _dados.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _dados.GetEnumerator();
}
T IDbSet<T>.Add(T entity)
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
T IDbSet<T>.Attach(T entity)
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
public T Create()
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
public System.Collections.ObjectModel.ObservableCollection<T> Local
{
get { throw new NotImplementedException("Derive a classe e este método para usar."); }
}
T IDbSet<T>.Remove(T entity)
{
throw new NotImplementedException("Derive a classe e este método para usar.");
}
}
}
Step 5: Create static classes in Models
to initialize your DbSets
fake
Let me give you an example of how a configuration of mine:
namespace MeuPrjeto.Testes.Models
{
public static class ColaboradoresConfiguration
{
public static IDbSet<Colaborador> MontarMockColaboradores()
{
return new FakeDbSet<Colaborador>
{
new Colaborador { ColaboradorId = Guid.NewGuid(), DataCriacao = DateTimeHelper.RandomDate(), DataNascimento = DateTimeHelper.RandomDate(), UltimaModificacao = DateTimeHelper.RandomDate() },
new Colaborador { ColaboradorId = Guid.NewGuid(), DataCriacao = DateTimeHelper.RandomDate(), DataNascimento = DateTimeHelper.RandomDate(), UltimaModificacao = DateTimeHelper.RandomDate() },
new Colaborador { ColaboradorId = Guid.NewGuid(), DataCriacao = DateTimeHelper.RandomDate(), DataNascimento = DateTimeHelper.RandomDate(), UltimaModificacao = DateTimeHelper.RandomDate() },
new Colaborador { ColaboradorId = Guid.NewGuid(), DataCriacao = DateTimeHelper.RandomDate(), DataNascimento = DateTimeHelper.RandomDate(), UltimaModificacao = DateTimeHelper.RandomDate() },
new Colaborador { ColaboradorId = Guid.NewGuid(), DataCriacao = DateTimeHelper.RandomDate(), DataNascimento = DateTimeHelper.RandomDate(), UltimaModificacao = DateTimeHelper.RandomDate() }
};
}
}
}
In the builder of Mock fake, would be like this:
private MeuProjetoFakeContext()
{
Colaboradores = ColaboradoresConfiguration.MontarMockColaboradores();
}
Step 6: Replace original project contexts with interfaces
Here are several ways to do it. What I did was put the context into a Controller basis as follows:
namespace MeuProjeto.Controllers
{
public abstract class Controller : System.Web.Mvc.Controller
{
protected IMeuProjetoContext Context;
protected Controller(IMeuProjetoContext _context = null)
{
// Aqui é o contexto de verdade mesmo que vai ser instanciado
Context = _context ?? new MeuProjetoContext();
}
...
}
}
Step 6: Assemble the Controllers test
Ideally, the Controllers test call the Controllers real, but passing to the Controller this Mock that we set up. A test case would look like this:
namespace MeuProjeto.Testes.Controllers
{
[TestClass]
public class ColaboradoresControllerTest
{
[TestMethod]
public void TestIndexView()
{
var fakeContext = new MeuProjetoFakeContext();
var controller = new ColaboradoresController(fakeContext);
var result = controller.Index("", null);
// Testes sempre usam Assert.
Assert.AreEqual("", result.ViewName);
}
}
}
This test is really silly. Just to show what you can do, and not leave the huge answer.
Method 2: Using a pre-existing base
This is much easier to do but has no unit test characteristics. It consists of passing to the context a Connection string pointing to another base, identical to the standard base of the system, and using it to perform tests, modify records, etc.
Completion
When in doubt, I end up using both methods, but with preference for the first at the beginning of development. For this I assemble two test projects per system, and if I want to carry out the same tests using different bases, I derive a third project containing only the test classes.
I don’t have much experience with this and I won’t answer but you’re absolutely right. This frameworks or even methodologies require doing terrible things with code to solve things they care about. For me, for what I understand and like, none of these solutions are acceptable. Complicating code to make it testable in established patterns is a bad idea. Actually, I’ve said a few times that you can test without doing these things. It just takes more work on the test. But I think it’s better than designing with the test in mind. Testability should be a consequence, not a requirement
– Maniero
@bigown, would you be referring indirectly to Design Patterns?
– ptkato
Eventually, but not primarily. Other techniques are more damaging. SOLID for example. Everything in it can be good if you need for the code to meet real needs. But I often see people applying because they’re told to, because they help with some of the secondary things, like to facilitate the test. Testing is good, but putting penduricalhos just to give the impression of low cost of testing, I am against.
– Maniero
Excellent question.
– Leonel Sanches da Silva
The way to test depends on what you want to test. Testing a class has no value - what we test are behaviours. If you can describe the classes involved and the purpose of the test (which behavior you want to test) it is easy to respond with an appropriate testing technique. I already tell you that hardly the solution will imply the use mocks. Mocks, mainly those coming in frameworks are highly complex and very little useful.
– Caffé
For what I see you don’t need Mocks but rather of Fakes. Therefore, create a manual Budget and a couple of Products is enough to make the tests.
– ramaral
@ramaral even if my Budget class is very complex and depending on other classes like products for example and this is also complex? Because today I do everything manually as you say, but I am in trouble when I need to modify these dependent classes and I need to update these fakes, even if the modification is not relevant to my Sell class and I thought mocks would be more appropriate.
– Ricardo Kenji Harasaki
When you just need data use Fakes. When the class under test depends on results of methods from other classes(dependencies) use Mocks. Note that to create a Mock you just need the Interface or it is possible to create Mocks without having the class implemented. If your classes have many dependencies (in-depth) consider using an Ioc Container.
– ramaral
Unit testing is a laborious thing, the cost/benefit ratio of doing it should always be considered. If you choose to do so I advise you to start by writing the test first and then the code (TDD).
– ramaral
That class
Venda
is a Model?– Leonel Sanches da Silva
@Yes, it’s a model, I’m trying to follow the proposal of Domain-Driven Design
– Ricardo Kenji Harasaki
Bad idea. I think I already know where you got the initiative. Her author came to discuss with me these days. I recommend you start with the simplest, understand well the MVC and then diversify your application. Don’t put complexity in the system where you don’t need it. A Model MVC is not a classic POCO.
– Leonel Sanches da Silva
@Ciganomorrisonmendez the author of the book Domain-Driven Design, Eric Evans? I am looking to simplify my business rules as much as possible and to leave them independent of anything else (BD, Views), for organization and maintenance purposes and I thought it would match the proposal to develop thinking in the domain of the problem. And as far as I know, I can use MVC very well for this, being the View and Controller in a layer more external and the Model in its core (Onion Architeture), being able to focus still in my domains.
– Ricardo Kenji Harasaki
I wrote a text about it, which has not yet been published. I argue that the gain of implementing the DDD in MVC is zero. All this behavioral logic that you put in the class is done differently in MVC, and the supposed gain in organization and maintenance does not exist, even because MVC needs an organizational standard to function properly, and the model of abstraction makes you focus on what is important, which are the rules of business, and not on maintenance details. I said it before and I’ll say it again: it’s best to abandon this idea of DDD in MVC.
– Leonel Sanches da Silva
Interesting! Please send the link where your article will be published to read please @Ciganomorrisonmendez! Thanks for the clarification
– Ricardo Kenji Harasaki
@Ricardokenjiharasaki Out already: http://masterdesigners.com.br/quando-boa-pratica-vira-ma-pratica/
– Leonel Sanches da Silva
@Gypsy omorrisonmendez link broke, has how to update?
– Math
@Math I think the text is completely lost. I have another similar text here.
– Leonel Sanches da Silva