I'm trying to write a unit test which uses the inmemorydbcontext of entity framework core:
Setup of the InMemoryDbContext:
public abstract class ServiceTestsBase<TContextInterface, TContext>
where TContextInterface : IDbContext
where TContext : DbContext, TContextInterface
{
protected TContextInterface InMemoryContext { get; private set; }
public ServiceTestsBase()
{
this.InitializeInMemoryContext();
}
protected void InitializeInMemoryContext()
{
var httpContextAccessorFake = new Mock<IHttpContextAccessor>();
var randomAuditDatabaseName = $"{nameof(IAuditingContext)}_{Guid.NewGuid()}";
var auditOptions = new DbContextOptionsBuilder<AuditDbContext>().UseInMemoryDatabase(randomAuditDatabaseName).Options;
var auditContext = new AuditDbContext(auditOptions);
var randomDatabaseName = $"{nameof(TContextInterface)}_{Guid.NewGuid()}";
var options = new DbContextOptionsBuilder<TContext>().UseInMemoryDatabase(randomDatabaseName).Options;
this.InMemoryContext = (TContext) Activator.CreateInstance(typeof(TContext), options, httpContextAccessorFake.Object, auditContext);
}
protected int AddToInMemoryContext<TEntity>(ICollection<TEntity> entities) where TEntity : BaseEntity
{
foreach (var entity in entities)
{
this.InMemoryContext.Add(entity);
}
return this.InMemoryContext.SaveChangesAsync().Result;
}
my Unit Test:
[TestMethod]
public void SaveProjektStatus_WhenOldPhaseStatusWasAbgebrochenAndNewStatusIsAbgebrochen_ThenShouldNotSendMail()
{
var ppmProjektId = new Guid("0A1D9BCF-01BA-418E-81D8-FD5EFFD79046");
var projektStatusId = new Guid("4600DEEF-09DB-4CB3-99CF-4D3BCBA22712");
var ppmProjekt = new PPMProjekt
{
Id = ppmProjektId
};
var oldProjektStatus = new PPMProjektStatus
{
Id = projektStatusId,
PPMProjektId = ppmProjektId,
PhaseStatusId = PPMCodeConst.PPM_PHASESTATUS_ABGEBROCHEN.Id
};
this.AddToInMemoryContext(new[] { ppmProjekt });
this.AddToInMemoryContext(new[] { oldProjektStatus });
var newProjektStatus = new PPMProjektStatus
{
Id = projektStatusId,
PPMProjektId = ppmProjektId,
PhaseStatusId = PPMCodeConst.PPM_PHASESTATUS_ABGEBROCHEN.Id
};
emailServiceFake.Setup(x => x.GetEmailTemplateById(PPMEmailVorlageConst.PPM_PROJECT_STATUS_CHANGED.Id))
.Returns(new EmailVorlage
{
SubjectDt = "{0}{1}",
BodyDt = "{0}{1}",
DefaultEmpfaenger = "testmail@test.com"
});
this.testee.SaveProjektStatus(newProjektStatus);
emailServiceFake.Verify(x => x.SendEmail(It.IsAny<EMail>()), Times.Never());
}
The method to be tested:
public void SaveProjektStatus(PPMProjektStatus projektStatus)
{
var oldProjektStatus = db.PPMProjektStatus.AsNoTracking().SingleOrDefault(x => x.Id == projektStatus.Id);
var personalInternTagessatz = db.PPMProjekt.Single(x => x.Id == projektStatus.PPMProjektId).PersonalInternTagessatz_CHF;
projektStatus.PersonalInternFachIST_CHF = personalInternTagessatz * projektStatus.PersonalInternFachIST_PT;
projektStatus.PersonalInternFachHochrechnung_CHF = personalInternTagessatz * projektStatus.PersonalInternFachHochrechnung_PT;
projektStatus.PersonalInternInformatikIST_CHF = personalInternTagessatz * projektStatus.PersonalInternInformatikIST_PT;
projektStatus.PersonalInternInformatikHochrechnung_CHF = personalInternTagessatz * projektStatus.PersonalInternInformatikHochrechnung_PT;
db.AddOrUpdate(projektStatus);
db.SaveChanges();
if (projektStatus.PhaseStatusId.Equals(PPMCodeConst.PPM_PHASESTATUS_ABGEBROCHEN.Id)
&& (oldProjektStatus == null || !oldProjektStatus.PhaseStatusId.Equals(PPMCodeConst.PPM_PHASESTATUS_ABGEBROCHEN.Id)))
{
NotifyProjectStatusAbgebrochen(projektStatus.PPMProjekt.Nummer, projektStatus.PPMProjekt.Bezeichnung, projektStatus.GeaendertAm);
}
if (projektStatus.PhaseStatusId.Equals(PPMCodeConst.PPM_PHASESTATUS_UEBERFUERTIN.Id))
{
var projektUeberfuertIn = db.PPMProjekt.Where(x => x.Id == projektStatus.ProjektUeberfuertIn).FirstOrDefault();
NotifyProjectStatusUeberfuertIn(projektStatus.PPMProjekt.Nummer, projektStatus.PPMProjekt.Bezeichnung, projektStatus.GeaendertAm, projektUeberfuertIn.Nummer, projektUeberfuertIn.Bezeichnung);
}
searchService.RebuildProjekteSearchIndex(projektStatus.PPMProjektId);
}
The test fails at the line: db.AddOrUpdate(projektStatus); with this message:
System.InvalidOperationException: The instance of entity type 'PPMProjektStatus' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Why is it throwing this exception? It only fails in the unit test, it works when I run the program manually. I explicitely used .AsNoTracking() to avoid this issue but it seems like the InMemoryDatabase doesn't care about it...
Thanks in advance
Edit: I've also tried to only load the ID instead of the whole object like this
var oldProjektStatus = db.PPMProjektStatus.SingleOrDefault(x => x.Id == projektStatus.Id)?.PhaseStatusId;
but I still get the same error...
question from:https://stackoverflow.com/questions/66065877/entity-framework-core-unit-test-entity-cannot-be-tracked