.Net Core 2.2 / EFC 2.2.3 / Pomelo.EntityFrameworkCore.MySql 2.2.0
Imagine that you have a table called Colors with some predefined data.
public void Configure(EntityTypeBuilder<Color> builder)
{
builder.ToTable("Colors");
builder.HasKey(r => r.Id).UseMySqlIdentityColumn();
builder.Property(r => r.Name).IsRequired().HasMaxLength(255);
builder.Property(v => v.RGB).IsRequired().HasMaxLength(7);
builder.HasData(GetSeed());
}
private ICollection<Color> GetSeed()
{
return new List<Color>()
{
new Color(){Id=1, Name="Black", RGB="#000"},
new Color(){Id=2, Name="White", RGB="#fff"},
}
}
One of my tests is to test the CreateColorCommandHandler. Very straightfoward
var Context = CBERPContextFactory.Create();
var query = new CreateColorCommandHandler(Context);
var command = new CreateColorCommand();
command.Name= "Random color";
command.RGB = "#001122";
var colorId = await query.Handle(command, CancellationToken.None);
//Assert
Assert.IsInstanceOf<long>(colorId);
Assert.NotZero(colorId);
var cor = Context.Colors.Where(p => p.Id == colorId).SingleOrDefault();
Assert.NotNull(cor);
Assert.AreEqual(command.Name, cor.Name);
Assert.AreEqual(command.RGB, cor.RGB);
CBERPContextFactory.Destroy(Context);
//>>> Handle simply add a new entity without informing ID
Handle method
public async Task<long> Handle(CreateColorCommand request, CancellationToken cancellationToken)
{
var entity = new Color
{
Name = request.Name,
RGB = request.RGB,
};
_context.Colors.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
When I ran this test I get the error An item with the same key has already been added. Key: 1. Which means that InMemoryDatabase do not has auto increment feature.
Am I writing the test wrong?
How can I test case like this? I want to make sure that the command is OK.
Probably I am missing some very basic rule here.
I assume problem is in the following line:
var Context = CBERPContextFactory.Create();
May be you are using the same context instance for multiple tests. According to Testing with InMemory documentation:
Each test method specifies a unique database name, meaning each method has its own InMemory database.
So make sure that your each and every test method has a distinct context instance.
If still does not work then try setting the identity key value manually because InMemory database may does not support auto-increment.
InMemoryDatabase do not have all features yet, and AUTO INCREMENT one of those that need improvements: https://github.com/aspnet/EntityFrameworkCore/issues/6872
Not the answer I wanted, but is the one working for now: clear all seeds before testing.
private static void Clear(this DbContext context)
{
var properties = context.GetType().GetProperties();
foreach (var property in properties)
{
var setType = property.PropertyType;
bool isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
if (!isDbSet) continue;
dynamic dbSet = property.GetValue(context, null);
dbSet.RemoveRange(dbSet);
}
context.SaveChanges();
}
Related
I am aware that such question has already been asked, but solution did not help me.
[Fact]
public async Task UpdateAsync()
{
string newTitle = "newTitle1";
int newBrandId = 3;
var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
item.BrandId = newBrandId;
item.Title = newTitle;
storeContext.Entry(item).State = EntityState.Detached;
await service.UpdateAsync(item); // exception inside
var updatedItem = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
Assert.Equal(newTitle, updatedItem.Title);
Assert.Equal(newBrandId, updatedItem.BrandId);
}
public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified; // exception when trying to change the state
await _dbContext.SaveChangesAsync();
}
Message: System.InvalidOperationException : The instance of entity type 'Item' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
interesting that exception is the same even if no item retreived from db, like so
//var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
var item = new Item()
{
Id = 1,
BrandId = newBrandId,
CategoryId = 1,
MeasurementUnitId = 1,
StoreId = 1,
Title = newTitle
};
Had the same problem with EF core 2.2. I never experianced this with other applications.
Ended up rewriting all my update functions somehow like this:
public bool Update(Entity entity)
{
try
{
var entry = _context.Entries.First(e=>e.Id == entity.Id);
_context.Entry(entry).CurrentValues.SetValues(entity);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
// handle correct exception
// log error
return false;
}
}
Alexandar's answer, which was to disable tracking completely, solved my issue, but I got worried since I didn't know what this would do to the rest of my application. So I went to the Microsoft docs and found this:
You should not disable change tracking if you want to manipulate entity instances and persist those changes to the database using SaveChanges().
This method sets the default behavior for all contexts created with these options, but you can override this behavior for a context instance using QueryTrackingBehavior or on individual queries using the AsNoTracking(IQueryable) and AsTracking(IQueryable) methods.
So the solution for me was to disable tracking only when needed. So I solved my issue by using this in the other part of my code that retrieved the same entry from the database:
var entry = await context
.SomeDbTable
.AsNoTracking() // this is what you're looking for
.Find(id);
Numerous issues I've been running into have one nasty root.
In a nutshell: I've learned the hard way why dbContext is scoped rather than singleton. Here is Store type, but the issue was the same.
Here is simplified test initialization code
public TestBase()
{
services = new ServiceCollection();
storeContext = StoreContextMock.ConfigureStoreContext(services, output);
serviceProvider = services.BuildServiceProvider();
}
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
storeContext .Stores.Add(new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" });
storeContext .Stores.Add(new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" });
storeContext .SaveChanges();
return storeContext ;
}
I reread error and finally noticed the main word
The instance of entity type 'Store' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
So there has to be some orphan tracked instance preventing me from working with store. I did not save any references to s1 or s2, so it must be storeContext storing references on inserted objects even after leaving scope of their declaration and initialization. That's why I was unable update variables normally and also why my 'queried' from db objects had all their navigation properties assigned (lazy loading has little to do with this). The following code resolved all my issues.
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
services.AddDbContext<StoreContext>(c =>
c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<StoreContext>();
var s1 = new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john#mail.com" };
var s2 = new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer#mail.com" }
storeContext .Stores.Add(s1);
storeContext .Stores.Add(s2);
storeContext .Entry<Store>(s1).State = EntityState.Detached;
storeContext .Entry<Store>(s2).State = EntityState.Detached;
storeContext .SaveChanges();
return storeContext ;
}
That is one of many reasons why dbContext should be limited by a scope.
Thanks for the hint.
For me was this the solution:
public void Update(int id, T obj)
{
var entry = table.Find(id);
_context.Entry(entry).CurrentValues.SetValues(obj);
}
Based on the solution Bryan gave. I think I use newer version of EF/Automapping. This works for me.
I got some similar error when I wanted to update data, and I found out I could fix it by clearing the property context. Here is what a did. It's not the same problem but it's the same error, so I think it can be fixed the same way. Clearing the context seems to be a good solution because it's the reason of whats happening.
context.ChangeTracker.Clear();
context.Cliente.Update(cliente);
context.SaveChanges();
I had same problem while I was copying some records in database by Entity Framework and changing one column that was other's entity key.
Tracking mode change did not fix the issue.
The issue was fixed by properly setting primary key in EntityTypeConfiguration, to contain the changed value here described as x.EntityTwoKey.
builder.HasKey(x => new { x.EntityOneKey, x.EntityTwoKey });
In my case I hit this error when running SaveChanges twice inside of two IFs statements. I moved the SaveChanges outside of those two blocks of code. Just a side note in my service layer it is querying the data with AsNoTracking();
if (user.SendPaymentStatus)
{
user.SendPaymentStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetPaymentHTML(user.MasterNodeName, user.Payee, DbMasterNode.LastPaidUtc);
Framework.Email.SendEmail(email, "MasterNode Payment - " + user.MasterNodeName, msg);
}
if (user.SendNodeStatus)
{
user.SendNodeStatus = false;
saveChanges = true;
//_userService.SaveChanges(user, false);
msg = GetStatusHTML(user.MasterNodeName, user.Payee, DbMasterNode.CurrentStatus, DbMasterNode.LastSeenUtc);
Framework.Email.SendEmail(email, "MasterNode Down - " + user.MasterNodeName, msg);
}
if (saveChanges)
{
user.SendPaymentStatus = false;
_userService.SaveChanges(user, false);
}
I was getting the same problem when was trying to update the value. then i found the proble i was using this.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")),ServiceLifetime.Singleton);
then i remove lifetime and it worked well for me.
services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")));
In my case above issue was resolved after I set primary key column Id as an Identity column.
We recently run into the same issue when adding multiple new items with identity column id set to 0. We are using OracleDataAccess client for EF core 3, we set the sequence number for the new entities when we do saveChanges(), but it errors out when we try to add() if there's already another item with id=0.
The fix we did is making sure the configuration for the identity column is correct:
1.) Set the key
builder.HasKey(t => t.Id);
2.) Set the database generate option correctly
[Column("ID"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int Id { get; set; }
or fluent equivalent:
builder.Property(t => t.Id)
.ValueGeneratedOnAdd();
We did not do second step correctly and was setting as DatabaseGeneratedOption.None, then EF core failed on add.
I have build a WebAPI and want to create a unit test project to have my services tested automatically.
The flow of my WebAPI is simple:
Controller (DI Service) -> Service (DI Repository) -> _repo CRUD
Suppose I have a service like:
public int Cancel(string id) //change status filed to 'n'
{
var item = _repo.Find(id);
item.status = "n";
_repo.Update(item);
return _repo.SaveChanges();
}
And I want to build a unit test, which just use InMemoryDatabase.
public void Cancel_StatusShouldBeN() //Testing Cancel() method of a service
{
_service.Insert(item);
int rs = _service.Cancel(item.Id);
Assert.Equal(1, rs);
item = _service.GetByid(item.Id);
Assert.Equal("n", item.status);
}
I've searched other related question, found that
You can't use dependency injections on test classes.
I just want to know if there is any other solution to achive my unit test idea?
When unit testing, you should just supply all the dependencies of the class you are testing explicitly. That is dependency injection; not having the service construct its dependencies on its own but making it rely on the outer component to provide them. When you are outside of a dependency injection container and inside a unit test where you are manually creating the class you are testing, it’s your responsibility to provide the dependencies.
In practice, this means that you either provide mocks or actual objects to the constructor. For example, you might want to provide a real logger but without a target, a real database context with a connected in-memory database, or some mocked service.
Let’s assume for this example, that the service you are testing looks like this:
public class ExampleService
{
public ExampleService(ILogger<ExampleService> logger,
MyDbContext databaseContext,
UtilityService utilityService)
{
// …
}
// …
}
So in order to test ExampleService, we need to provide those three objects. In this case, we will do the following for each:
ILogger<ExampleService> – we will use a real logger, without any attached target. So any call on the logger will work properly without us having to provide some mock, but we do not need to test the log output, so we do not need a real target
MyDbContext – Here, we’ll use the real database context with an attached in-memory database
UtilityService – For this, we will create a mock which just setups the utility method we need inside the methods we want to test.
So a unit test could look like this:
[Fact]
public async Task TestExampleMethod()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
// using Moq as the mocking library
var utilityServiceMock = new Mock<UtilityService>();
utilityServiceMock.Setup(u => u.GetRandomNumber()).Returns(4);
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<Customer>().Add(new Customer()
{
Id = 2,
Name = "Foo bar"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new ExampleService(logger, db, utilityServiceMock.Object);
// act
var result = service.DoSomethingWithCustomer(2);
// assert
Assert.NotNull(result);
Assert.Equal(2, result.CustomerId);
Assert.Equal("Foo bar", result.CustomerName);
Assert.Equal(4, result.SomeRandomNumber);
}
}
In your specific Cancel case, you want to avoid using any methods of the service you are not currently testing. So if you want to test Cancel, the only method you should call from your service is Cancel. A test could look like this (just guessing the dependencies here):
[Fact]
public async Task Cancel_StatusShouldBeN()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<SomeItem>().Add(new SomeItem()
{
Id = 5,
Status = "Not N"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new YourService(logger, db);
// act
var result = service.Cancel(5);
// assert
Assert.Equal(1, result);
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
var item = db.Set<SomeItem>().Find(5);
Assert.Equal(5, item.Id);
Assert.Equal("n", item.Status);
}
}
Btw. note that I’m opening up a new database context all the time in order to avoid getting results from the cached entities. By opening a new context, I can verify that the changes actually made it into the database completely.
I'm running into some issues testing Fluent NHibernate's persistence. I'm not sure if this is simply poor understanding on my part or improper expectations of the test. If so, does anyone have any advice on how best to set up a Unit Test for this part of the DAL?
I have a pair of classes Client and Facility with a one-to-many relationship:
One: Client can have Many Facility
Using this FluentNHibernate's mapping structure, I'd expected they should look like this:
public class ClientMapping : DataMapping<Client>
{
public ClientMapping()
{
HasMany(client => client.Facilities)
.Inverse()
.Cascade
.All();
}
}
public class FacilityMapping : DataMapping<Facility>
{
public FacilityMapping()
{
References(fac => fac.Owner);
}
}
I followed FNH's advice on creating tests such as below but when running it- I get a Client table with 2 Clients and a Facility table with two different Ids, even though I'm passing in a single object.
[Test]
public void CanCorrectlyCreateFacilityTable()
{
_client = new Client {Name = "Preston"};
new PersistenceSpecification<Facility>(session, new DataEqualityComparer())
.CheckProperty(f => f.Id, 1)
.CheckProperty(f => f.Name, _facility1.Name)
.CheckReference(f => f.Owner, _client)
.VerifyTheMappings();
new PersistenceSpecification<Facility>(session, new DataEqualityComparer())
.CheckProperty(f => f.Id, 2)
.CheckProperty(f => f.Name, _facility2.Name)
.CheckReference(f => f.Owner, _client)
.VerifyTheMappings();
}
Closest q/a I've found is those below but even when running the Client test first, I seem to get the same result (likely because the database state resets itself for each test):
Cascade persist creates duplicate rows?
Hibernate - one to Many relationship
It turns out my expectations were incorrect. The Persistence Specification test simply tests where data hits the database - hence, it'll send new items each time it's run.
To test whether the mappings are cascading data correctly I needed to write a test like below:
[Test]
public void CanSaveAndLoadFacilityMapping()
{
object id;
object id2;
using (var trans = _session.BeginTransaction())
{
id = _session.Save(_facility1);
id2 = _session.Save(_facility2);
trans.Commit();
}
_session.Clear();
using (var trans = _session.BeginTransaction())
{
var facility = _session.Get<Facility>(id);
var facility2 = _session.Get<Facility>(id2);
Assert.AreEqual(facility.Name, _facility1.Name);
Assert.AreEqual(facility.Owner.Name, _client.Name);
Assert.AreEqual(facility2.Owner.Name, _client.Name);
Assert.AreEqual(facility.Owner.Id, facility2.Owner.Id);
trans.Dispose();
}
}
I am unit-testing an async method that returns a List<T>. This method has a dependency on a mapping class/interface. In my unit-test, I am mocking the mapping class using moq. The test runs okay, and the returned list has items, but the values of the items is null. I think the problem is because I haven't stubbed-out the mapping classes methods properly. I don't have a lot of experience with testing, so any guidance is appreciated.
Test Method:
[TestMethod]
[TestCategory("CSR.Data.Tests.Services.ServiceSearchTest")]
public void SearchAccount()
{
// Arrange
var mapper = new Mock<CSR.Data.Mapping.Interfaces.IMapper<Account, AccountDTO>>();
mapper.Setup(i => i.Initialize());
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns(It.IsAny<Account>);
mapper.Setup(i => i.DomainToDto(It.IsAny<Account>())).Returns(It.IsAny<AccountDTO>);
var service = new ServiceSearch(null,mapper.Object);
string accountNumber = "123";
string accountName = "";
// Act
var results = service.SearchAccount(accountNumber, accountName);
// Assert
Assert.IsTrue(results.Result.Count >= 1);
}
Method/Class That I'm Testing:
public class ServiceSearch : IServiceSearch
{
public ServiceSearch(IMapper<Claim, ClaimDTO> claimMapper, IMapper<Account, AccountDTO> accountMapper)
{
_claimMapper = claimMapper;
_accountMapper = accountMapper;
}
public async Task<List<AccountDTO>> SearchAccount(string accountNumber, string accountName)
{
var accounts = new List<Account>();
var accountDTOs = new List<AccountDTO>();
var results = await Task.Run(() => base.AccountSearch(accountNumber, accountName).Result);
if (results != null && results.Count > 0)
{
//Map DH to Domain
_accountMapper.Initialize();
foreach (AccountSearchResult result in results)
{
accounts.Add(_accountMapper.ToDomain(result));
}
//Map Domain to DTO
foreach (Account account in accounts)
{
accountDTOs.Add(_accountMapper.DomainToDto(account));
}
}
return accountDTOs;
}
}
This isn't the best place to use a Mock object because you are going to spend a lot of time writing your test objects and mock results. The issue with the setup call is that you haven't configured anything to send back in the result. A correct example would be:
// you would fully configure this object
AccountDTO expectedResult = new AccountDTO();
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns(expectedResult);
Now you can use the setup to configure different accountDTOs for different inputs.
You call also configure a callback to generate the account at test time:
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns<AccountSearchResult>(sr => {
// build and return your dto here
});
However, unless your mapper is expensive to run or create, I think you'd better off just ensure that it is fully tested and acceptable and then use it to go ahead and generate the DTOs directly instead of trying to mock it out.
You don't actually setup an object in the ".Returns" call. You need to make sure to setup the ".Returns" to actually have an object with values.
I am developing Moq tests for various entities. I can setup create and delete tests fine, but not update - the entity in the repository does not change. I know this is due to the PersistAll doing nothing (probably due to a setup I am missing).
This is a sample of an insert persist setup (I am looking for an Update version):
agg.Setup(a => a.InsertOnPersist<Thingy>(model)).Callback(() => mockThingies.Add(model));
In addition, I also have this to link the List to being the repository:
agg.Setup(a => a.GetObjectStore<Artist>()).Returns(mockThingies.AsQueryable());
This is a sample of an update test I have:
public List<Thingy> mockThingies; //this is our repository
[TestInitialize]
public void SetupTests()
{
mockThingies= new List<Thingy>();
Thingy someThingy = new Thingy();
someThingy.Name = "MyName";
someThingy.ID = 1;
mockThingies.Add(someThingy);
}
[TestMethod]
public void CanEditExistingThingy()
{
Mock<BusinessExceptionBroadcaster> beb = new Mock<BusinessExceptionBroadcaster>();
Mock<IValidationEngine> valid = new Mock<IValidationEngine>();
Mock<IAggregate> agg = new Mock<IAggregate>();
agg.Setup(a => a.GetObjectStore<Thingy>()).Returns(mockThingies.AsQueryable());
ThingyRepository repo = new ThingyRepository (agg.Object);
ThingyService service = new ThingyService (repo, beb.Object, valid.Object);
Thingy newThingy = new Thingy();
newThingy.ID = 1; //same as old
newThingy.Name = "newname"; //new name
Assert.AreNotEqual(newThingy.Name,mockThingies[0].Name);
Assert.IsTrue(service.Update(newThingy));
Assert.AreEqual(newThingy.Name, mockThingies[0].Name); //FAILS HERE
}
This is the code to update:
public bool Update(Thingy entity)
{
Thingy existingThingy= _Thingy.FirstOrDefault(t=>t.ID == entity.ID);
if (existingThingy != null)
{
_Thingy.PersistAll();
return true;
}
else
{
//unimportant
}
}
return false;
}
Don't worry about testing whether the update call actually updates something. You'll just want to verify that your service calls the appropriate method on the repo to perform the update and persist. Testing the actual update is a little outside the scope of this one test.
As far as I can see, it can't work, because you're setting one Thingy with ID=1 in Setup, and then create other one with same ID in test. Although they share same ID, they are not same, so your changes can't be ever propagated to repository.
In fact, I think that it's a bug in your CUT code, because while you're testing ID match, you don't test that your repository knows something about entity you're updating. To add, I personally think that there's something wrong with your repository design if it allows such things.
If we were talking about EntityFramework I'd say you have to attach your entity to context.