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();
}
}
Related
.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();
}
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 running some integration tests against a database. I want to setup the database with seed data, run my tests, and then delete the database for every test (so each test has a fresh slate). I'm currently using these setup/teardown methods to do it:
private ProjectDbContext db;
[TestInitialize]
public void SetUp()
{
db = new ProjectDbContext("TestConnection");
(new SeedData()).Run(db); //Seed Data
}
[TestCleanup]
public void Teardown()
{
db.Database.Delete();
db.Dispose();
}
My problem is that it takes a little over a half second per test and I'd like to see better performance. Any thoughts? Anyone have a better strategy?
According to your code, I understand that you want to delete the data you inserted into the database for testing. I have done it in slightly different way but it might be helpful to you. We can use TransactionScope instead of deleting the data manually. Image of code
You can also visit here for details of the approach.
You could do something like:
public static void ClearDatabase(DbContext context)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var entities = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace).BaseEntitySets;
var method = objectContext.GetType().GetMethods().First(x => x.Name == "CreateObjectSet");
var objectSets = entities.Select(x => method.MakeGenericMethod(Type.GetType(x.ElementType.FullName))).Select(x => x.Invoke(objectContext, null));
var tableNames = objectSets.Select(objectSet => (objectSet.GetType().GetProperty("EntitySet").GetValue(objectSet, null) as EntitySet).Name).ToList();
foreach (var tableName in tableNames)
{
context.Database.ExecuteSqlCommand(string.Format("DELETE FROM {0}", tableName));
}
context.SaveChanges();
}
If you have no constraints and it's important that identity columns are set a certain way for your tests (which I'd recommend against), you could use TRUNCATE instead of DELETE FROM.
(code for deletion from here.)
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.
I'm setting up a new project and have gotten NHibernate to work with structuremap...sorta. I'm using the NHibernate.Mapping.ByCode.Conformist setup with ClassMaps. No errors occur, but when I query over a session and there are records present in the database for a particular type, nothing comes back. Upon further examination, it appears that the mappings that I've set up for these types are not executing. Here's my code that wires up things for structuremap. I can confirm that it is being executed.
public class OrmRegistry : Registry
{
public OrmRegistry()
{
var sessionFactory = BuildSessionFactory();
For<ISessionFactory>().Singleton().Use(sessionFactory);
For<ISession>().HybridHttpOrThreadLocalScoped().Use(s => sessionFactory.OpenSession());
}
public ISessionFactory BuildSessionFactory()
{
var cfg = new Configuration().DataBaseIntegration(db =>
{
db.ConnectionStringName = "LocalSqlServer";
db.Dialect<MsSql2008Dialect>();
db.Driver<SqlClientDriver>();
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
db.IsolationLevel = IsolationLevel.ReadUncommitted;
db.BatchSize = 500;
}).AddAssembly(Assembly.GetExecutingAssembly());
if(HttpContext.Current != null)
{
cfg.CurrentSessionContext<WebSessionContext>();
}
else
{
cfg.CurrentSessionContext<ThreadStaticSessionContext>();
}
return cfg.BuildSessionFactory();
}
}
I'm nearly certain I'm just missing something extremely obvious here, but I've been looking at it for a few hours and haven't had any success. I also got downsized a couple days ago, so I don't have a coworker around to look at it.
Looks like you got your configuration initialized, but what about mapping? You need to initialize mappings like this (if you are using conventions):
var mapper = new ConventionModelMapper();
// TODO: define conventions here using mapper instance
// just an example on how I have been using it
var entities = ... // get all entity types here
cfg.AddDeserializedMapping(mapper.CompileMappingFor(entities), "MyMappings");
return cfg.BuildSessionFactory();
And another example if you are using mapping classes (from this post):
var mapper = new ModelMapper();
var mappingTypes = typeof (InvoiceMapping).Assembly.GetExportedTypes()
.Where(t => t.Name.EndsWith("Mapping")).ToArray();
mapper.AddMappings(mappingTypes);
cfg.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
return cfg.BuildSessionFactory();