I had the same issue (EF Core) while setting up xUnit tests. What 'fixed' it for me in testing was looping through the change tracker entities after setting up the seed data.
I set up a Test Mock Context:
/// <summary>
/// Get an In memory version of the app db context with some seeded data
/// </summary>
public static AppDbContext GetAppDbContext(string dbName)
{
//set up the options to use for this dbcontext
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: dbName)
//.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.Options;
var dbContext = new AppDbContext(options);
dbContext.SeedAppDbContext();
return dbContext;
}
Extension method to add some seed data:
foreach
loop at bottom of method. public static void SeedAppDbContext(this AppDbContext appDbContext)
{
// add companies
var c1 = new Company() { Id = 1, CompanyName = "Fake Company One", ContactPersonName = "Contact one", eMail = "[email protected]", Phone = "0123456789", AdminUserId = "" };
c1.Address = new Address() { Id = 1, AddressL1 = "Field Farm", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(c1);
var nc1 = new Company() { Id = 2, CompanyName = "Test Company 2", ContactPersonName = "Contact two", eMail = "[email protected]", Phone = "0123456789", Address = new Address() { }, AdminUserId = "" };
nc1.Address = new Address() { Id = 2, AddressL1 = "The Barn", AddressL2 = "Some Lane", City = "some city", PostalCode = "AB12 3CD" };
appDbContext.CompanyRecords.Add(nc1);
//....and so on....
//last call to commit everything to the memory db
appDbContext.SaveChanges();
//and then to detach everything
foreach (var entity in appDbContext.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
}
The controller put method
The .ConvertTo<>()
Method is an extension method from ServiceStack
[HttpPut]
public async Task<IActionResult> PutUpdateCompany(CompanyFullDto company)
{
if (0 == company.Id)
return BadRequest();
try
{
Company editEntity = company.ConvertTo<Company>();
//Prior to detaching an error thrown on line below (another instance with id)
var trackedEntity = _appDbContext.CompanyRecords.Update(editEntity);
await _appDbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException dbError)
{
if (!CompanyExists(company.Id))
return NotFound();
else
return BadRequest(dbError);
}
catch (Exception Error)
{
return BadRequest(Error);
}
return Ok();
}
and the test:
[Fact]
public async Task PassWhenEditingCompany()
{
var _appDbContext = AppDbContextMocker.GetAppDbContext(nameof(CompaniesController));
var _controller = new CompaniesController(null, _appDbContext);
//Arrange
const string companyName = "Fake Company One";
const string contactPerson = "Contact one";
const string newCompanyName = "New Fake Company One";
const string newContactPersonName = "New Contact Person";
//Act
var getResult = _controller.GetCompanyById(1);
var getEntity = (getResult.Result.Result as OkObjectResult).Value;
var entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(companyName, entityDto.CompanyName);
Assert.Equal(contactPerson, entityDto.ContactPersonName);
Assert.Equal(1, entityDto.Id);
//Arrange
Company entity = entityDto.ConvertTo<Company>();
entity.CompanyName = newCompanyName;
entity.ContactPersonName = newContactPersonName;
CompanyFullDto entityDtoUpd = entity.ConvertTo<CompanyFullDto>();
//Act
var result = await _controller.PutUpdateCompany(entityDtoUpd) as StatusCodeResult;
//Assert
Assert.True(result.StatusCode == 200);
//Act
getResult = _controller.GetCompanyById(1);
getEntity = (getResult.Result.Result as OkObjectResult).Value;
entityDto = getEntity as CompanyFullDto;
//Assert
Assert.Equal(1, entityDto.Id); // didn't add a new record
Assert.Equal(newCompanyName, entityDto.CompanyName); //updated the name
Assert.Equal(newContactPersonName, entityDto.ContactPersonName); //updated the contact
//make sure to dispose of the _appDbContext otherwise running the full test will fail.
_appDbContext.Dispose();
}