I'll go through my testing way. I do not know if it's the best, but it can be a starting point for us to produce something more concise and coherent.
The method of testing and Mocks of Gypsy
It took me a long time to find something that was good enough for a Mock and I never found it. The texts I come across about it usually talk about everything and do not explain anything, so I decided to make some homemade Mocks that work well for my case.
I'm going to go through a series of steps to produce a test model quickly.
Method 1: Not using a pre-existing database
This method is more interesting from the point of view of the test because it does not have an already ready base and its addictions.
Step 1: Extract the Interface from DbContext
and use IDbSet
instead of DbSet
Extracting the context interface is simple:
- In your context class, right-click 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.
It will look something like this:
namespace MeuProjeto.Models
{
public class MeuProjetoContext : DbContext, MeuProjeto.Models.IMeuProjetoContext
{
...
}
}
Now replace all occurrences of DbSet
with IDbSet
. This makes the system and the test project use the same context, but with different DbSet
ployments.
That is, 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
This part does not have much secrecy:
Step3:CreateaLieContextinModels
(intestdesign)
I'massumingthathereyou'vealreadyaddedthereferenceofthemainprojectinthetestproject(right-clickReferenceAddReference...).>
AlsoinstalltheEntityFrameworkinthetestproject.JustaddingthereferencetoSystem.Entity.Data
doesnothelp.
Mylookslikethis:
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 updates to the interface you make will make you upgrade the lie context interface with just one click.
Step 4: Implement a% generic
My looks 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 FakeDbSet
to initialize their Models
of lie
I'll give an example of how my configuration was:
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 lie Mock constructor, it would look like this:
private MeuProjetoFakeContext()
{
Colaboradores = ColaboradoresConfiguration.MontarMockColaboradores();
}
Step 6: Replace the original project contexts with interfaces
Here are several ways to do it. What I did was put the context in a Controller base 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: Mount the Test Controllers
Ideally, test Controllers call the actual Controllers , but passing to the Controller this Mock we set. 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 pretty silly. Just to show what you can do, and not leave the answer huge.
Method 2: Using a pre-existing database
This is much easier to do, but does not have unit test features. It consists of passing a connection string pointing to another base, identical to the default system base, and using it to perform tests, modify records, etc.
Conclusion
In doubt, I end up using both methods, but with preference for the first one at the beginning of development. For this I put two test projects per system, and if I am interested in performing the same tests using different bases, I derive a third project containing only the test classes.