Mock DataAccessAdapter with UnitOfWork

Posts   
 
    
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 10-Nov-2011 02:17:01   

I'm attempting to mock IDataAccessAdapter for database persistence instead of wrapping my unit tests in transactions like I am currently doing. I.e. I don't what anything written to my unit test DB.

But I have hit trouble with UnitOfWork2.Commit(); as inside this there is this line: DataAccessAdapterBase adapterToUseAsBase = adapterToUse as DataAccessAdapterBase;

As adapterToUse is a Moq of IDataAccessAdapter and not a DataAccessAdapterBase adapterToUseAsBase is null and an exception is thrown on update/insert.

So instead I tried mocking DataAccessAdapterBase e.g var dataAccessAdapter = new Mock<DataAccessAdapterBase>(null); which got a bit further but now fails in DataAccessAdapterBase.PersistQueue and because PersistQueue is internal it can't be mocked by Moq.

Another path to go down would be to mock UnitOfWork2 but that is not possible with moq as GetObjectData is not virtual so Castle.DynamicProxy(used by Moq) complains 'The type SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2 implements ISerializable, but GetObjectData is not marked as virtual'

Btw there is a rule about this: CA2240: Implement ISerializable correctly- http://msdn.microsoft.com/en-us/library/ms182342.aspx

So am I correct in concluding that IDataAccessAdapter can't be mocked if UnitOfWork2 is be used - at least for update/insert?

And before anyone says it I know tests against a DB are more realistic, but sometimes you just don't care.

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 10-Nov-2011 10:36:54   

It won't work anyway. The main thing is: after uow.Commit(), the state of the entities will be the same as before, as the mock of the adapter hasn't done anything to it. So any checks after the commit won't work: the entities will still be marked as 'new' and 'dirty'.

Could you elaborate a bit why you want to mock the adapter instead of simply skipping it altogether?

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 10-Nov-2011 11:12:06   

Otis wrote:

It won't work anyway. The main thing is: after uow.Commit(), the state of the entities will be the same as before, as the mock of the adapter hasn't done anything to it. So any checks after the commit won't work: the entities will still be marked as 'new' and 'dirty'.

Not necessarily, for example if I mock it like this:

public static Mock<IDataAccessAdapter> MockDataServicesPortalDataAccessAdapter()
{
    var mockDataServicesataAccessAdapter = new Mock<IDataAccessAdapter>();
    mockDataServicesataAccessAdapter.Setup(das => das.DeleteEntity(It.IsAny<IEntity2>(), It.IsAny<IPredicateExpression>()))
                .Returns(true).Callback<IEntity2, IPredicateExpression>((e, p) => e.Fields.State = EntityState.Deleted);
    return mockDataServicesataAccessAdapter;
}

This will be true afterwards

Assert.AreEqual(EntityState.Deleted, entity.Fields.State);

Otis wrote:

Could you elaborate a bit why you want to mock the adapter instead of simply skipping it altogether?

How could I skip it?

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 11-Nov-2011 11:01:11   

By writing a test that assumes the save went OK. You're doing that now anyway. Deletes are rather easy, but how are you going to mock a prefetch path fetch of multiple nodes, or a hierarchical save action? the question then becomes: what is the reason for the test: to test your business logic (I would say so) or to test also if our code works. wink While I appreciate you testing our code again, you could say the tests you're writing are mainly meant to test whether your application code itself works.

So either skip the DB or include it in full, as only then you'll find bugs related to null values, UC violations, FK violations etc.

I can refactor the call away for you, but it won't be in v3.1, it will be v3.5, so the problem is currently not solveable, I'm afraid.

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 11-Nov-2011 13:05:29   

Otis wrote:

By writing a test that assumes the save went OK. You're doing that now anyway. Deletes are rather easy, but how are you going to mock a prefetch path fetch of multiple nodes, or a hierarchical save action? the question then becomes: what is the reason for the test: to test your business logic (I would say so) or to test also if our code works. wink While I appreciate you testing our code again, you could say the tests you're writing are mainly meant to test whether your application code itself works.

So either skip the DB or include it in full, as only then you'll find bugs related to null values, UC violations, FK violations etc.

I'll try and clarify, Yes this is about testing our business logic, I personally prefer to include the DB in full when I do this, probably because I spend a lot of my time in the DAL where I have no choice but to test against a DB and for the reasons you stated. But the rest of my team spend there time in the UI layer and generally prefer to skip the DB all together in their tests, which is fair enough I guess. However the way our DAL is structured skipping the DB is not easy and one guy in particular has gone as far as using @*&@# moles(to mock static methods) and a pseudo repository pattern to skip the DB which is ridiculous.

Anyway so I'm trying to meet them half way by letting them skip the DB (for DML at least) at the adapter level. At the very least this simply does nothing when a save is called, but as you alluded to there might be application code which relies on state change in the entity after the save, I think in most cases you could use a mock setup to set that state. The next thing you might want to verify is that the save was actually called, which you can do with something like

mockDataAccessAdapter.Verify(daa => daa.SaveEntity(occurrenceEntity), Times.Once());

or if the state was set by the mock you could check that. Verifying the save was called is a secondary consideration.

That’s DML anyway, for the query side I seriously doubt whether you could substitute anything but a basic Linq query with a result set at the adapter level but I haven't thought much about it yet. To my mind all queries need to be tested and tested against a db, but I guess you could argue a fetch-all-entities Linq query or a simple one entity fetch doesn't need to be tested so can be safely mocked out and substituted with some test data.

I have no interest in testing your code (except when I hit a bug of coursecry ) but I do wan't verification that our queries are correct e.g. if someone removes a where clause when they shouldn't a test should fail.

Otis wrote:

I can refactor the call away for you, but it won't be in v3.1, it will be v3.5

Sure, great - that's the best I could hope for.

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 14-Nov-2011 12:03:00   

Thanks for the explanation. Indeed, it makes sense.

The refactoring will be in v3.5, we're cleaning up a lot of code at the moment (removing clones between Selfservicing and adapter code) and I'll make sure this is done as well.

Frans Bouma | Lead developer LLBLGen Pro
TomDog
User
Posts: 623
Joined: 25-Oct-2005
# Posted on: 14-Nov-2011 16:33:37   

Otis wrote:

The refactoring will be in v3.5, we're cleaning up a lot of code at the moment (removing clones between Selfservicing and adapter code) and I'll make sure this is done as well.

Great!simple_smile I'n the mean time I do have a workaround for UnitOfWork.Commit - Have a UnitOfWork factory and make a descendant of UnitOfWork2 - say StubUnitWork and simply override Commit with an empty method and inject StubUnitWork when testing. This would obviously be unnecessary when you make the change in 3.5 but none the less would you consider adding a IUnitOfWork to the 2 UOW classes in 3.5 and/or making GetObjectData virtual in case there might be some other need to fake it?

Jeremy Thomas
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 15-Nov-2011 10:47:49   

I think the interface would be a good idea. We'll extract an interface and implement that onto the unitofwork classes (which likely will turn up to be 3: 1 base class with the most code and 2 thin classes for both paradigms.

Frans Bouma | Lead developer LLBLGen Pro