Recentemente ho iniziato a scavare nel test unitario di Entity Framework con Entity Framework 6 mocking.
Ho notato la seguente cosa:
Il mocking di Entity Framework mi obbliga a creare un contesto globale nella mia classe BL, ad esempio:
public class RefundRepayment : IDisposable
{
protected DbContext _dbContext = new DbContext();
/* more properties and class code */
public void Dispose()
{
_dbContext.Dispose();
}
}
Non riesco a capirlo, dato che preferirei implementare l'istruzione using
in ogni metodo per gestire DbContext
, il mio codice sarà simile a:
public class RefundRepayment
{
/* more properties and class code */
public void AccessDb()
{
using(DbContext dbContext = new DbContext())
{
/* db code here */
}
}
}
C'è qualche ragione specifica per cui dovremmo inizializzare un contesto globale invece di implementare l'istruzione using
?
Innanzitutto, è necessario utilizzare DI (tramite ninject, Unity, Core, ecc.) Per disattivarlo.
Lascia che ti mostri un semplice esempio di EF GetAll () che verifica il mio controller MVC.
[Fact]
public void GetAllOk()
{
// Arrange
// Act
var result = _controller.GetAll() as OkObjectResult;
// Assert
Assert.NotNull(result);
var recordList = result.Value as List<DTO.Account>;
Assert.NotNull(recordList);
Assert.Equal(4, recordList.Count);
}
Si basa su questo codice di avvio ...
public class AccountsControllerTests
{
DatabaseFixture _fixture;
AccountsControllerV1 _controller;
public AccountsControllerTests(DatabaseFixture fixture)
{
_fixture = fixture;
_controller = new AccountsControllerV1(_fixture._uow);
}
Che cos'è DatabaseFixture? Sono contento che tu abbia chiesto ...
public class DatabaseFixture : IDisposable
{
public ApplicationDbContext _context;
public DbContextOptions<ApplicationDbContext> _options;
public IUoW _uow;
public DatabaseFixture()
{
var x = Directory.GetCurrentDirectory();
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.Tests.json", optional : true)
.Build();
_options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProviderTests")
.Options;
_context = new ApplicationDbContext(_options);
_context.Database.EnsureCreated();
Initialize();
_uow = new UoW(_context);
}
private void Initialize()
{
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 1", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 2", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 3", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 4", AccountID = "", AccountUniqueID = "" });
_context.SaveChanges();
}
public void Dispose()
{
// Clean Up
_context.Database.EnsureDeleted();
}
}
[CollectionDefinition("Database Collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}
Alcune definizioni utilizzate nel codice precedente. Ho usato un modello di unità di lavoro che contiene riferimenti a tutti i miei archivi EF. Ho tenuto separate le classi Entity (database) e le classi DTO (Data Transfer Object). Ho usato una sostituzione in-memory per il database EF che inizializzo all'inizio di ogni esecuzione e / o test in modo che i miei dati siano sempre noti . Inserisco il Database Fixture nella mia classe di test (non ogni test), quindi non sto creando / distruggendo costantemente. Quindi creo il mio controller passando nella definizione del mio database UoW.
Il vero controllore richiede l'iniezione del contenitore UoW che hai creato con il vero database. Stai semplicemente sostituendo un ambiente di database controllato per il tuo test.
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
E sì, io uso la versione per gli occhi acuti. E sì, questo è un esempio di Core 2. Sempre applicabile per EF 6, è sufficiente la DI di terze parti;)
E il metodo di controllo che sto testando?
[HttpGet("accounts", Name ="GetAccounts")]
public IActionResult GetAll()
{
try
{
var recordList = _uow.Accounts.GetAll();
List<DTO.Account> results = new List<DTO.Account>();
if (recordList != null)
{
results = recordList.Select(r => Map(r)).ToList();
}
log.Info($"Providers: GetAccounts: Success: {results.Count} records returned");
return Ok(results);
}
catch (Exception ex)
{
log.Error($"Providers: GetAccounts: Failed: {ex.Message}");
return BadRequest($"Providers: GetAccounts: Failed: {ex.Message}");
}
}