MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

Dataportal, EF6.DbContextManager and MOQ #87

Open joemorin73 opened 8 years ago

joemorin73 commented 8 years ago

Having a wonderful time trying to implement the above title for Unit testing. I was curious if there is any documentation or sample to aid in this endeavor.

ajj7060 commented 8 years ago

What I've done is create an interface, which the DbContext implements, but instead of whatever EF specific types are returned, you return IQueryable for your tables (e.g, IQueryable<CustomerDto> Customers {get;}), where CustomerDto if your POCO which you've mapped in the context (or via the Mapping<T> class, IIRC).

Then you just code to the interface. The tricky part is getting an instance of the interface. For this I've gone a Service Locator route since you can't use ctor injection, and you don't really want access to the DB outside the DataPortal_XYZ methods anyway. Something like Common Service Locator can abstract away your specific container allowing you to Moq it away, or you can do something homegrown since its not that tough to abstract away your DI container.

The only gotcha is that your unit tests will be doing Linq to Objects, not Linq to EF, so where in EF you can query across navigation properties, attempting to do so with L2O may cause a NullReferenceException if you didn't explicitly create an instance in the navigation property.

The end result is that you just need to mock your service locator and configure it in your test; usually just one more line of code than if you were unit testing something that used ctor injection.

There are other approaches too like this one: http://aiampogi.blogspot.com/2013/08/cslanet-with-dependency-injection.html

And I think there was a Magenic employee's blog post about DI and Csla as well, but I can't find it at the moment.

JasonBock commented 8 years ago

Instead of using a service locator (which I'm really not fond of), here's another approach:

http://magenic.com/Blog/Post/43/Abstractions-in-CSLA

ajj7060 commented 8 years ago

@JasonBock Yes, that's the one I was looking for.

joemorin73 commented 8 years ago

Unfortunately, I'm not using the IoC or Servicelocator model. I'm using simply straight dataportal.

I've been able to get it operating by changing the DbSets instead of the context. It appears DbContext.GetManager doesn't allow you to change the DbContext at all.

joemorin73 commented 8 years ago

I'm hoping I am wrong, but am I to assume using Dataportal and DbContext.Getmanager will make unit testing close to impossible?

rockfordlhotka commented 8 years ago

I think it depends a lot on which layer of your application you are trying to test.

If you are trying to test the business layer then Jason's blog provides very good information on how to do that - by entirely abstracting the data portal out of the picture.

If you are trying to unit test your DAL then you shouldn't be invoking it from your business layer.

If you are trying to unit test your DP_XYZ methods then you need to implement your DAL interface in a way that can be abstracted for such testing - and yes, that would mean using something other than the DbContext from Csla.Data.

rockfordlhotka commented 8 years ago

Keep in mind that there is no requirement to use DbContext - it is just a helper provided by CSLA for one style of coding. If it doesn't meet your needs that's fine - use a different coding style that does meet your needs.

rockfordlhotka commented 8 years ago

All that said, if you have a proposal for a DbContext equivalent (that solves the problems it solves) that would also solve the problems you are encountering - then that's excellent and we'd very much welcome your contribution to making CSLA better in that regard.

joemorin73 commented 8 years ago

The goal of our unit tests are to test our business objects including the DataPortal_xyz calls. Since we are using the DbContextManager, our hope is to swap out the stored DbContext with a Mock version.

My current attempt is close as I am able to change the DbSets in the DbContext without mocking the DbContext.

Query tests succeed, but data manipulation appears to fail. I'm trying to determine where the problem is occurring am hoping that the DbContext does not need to be mocked itself due to the problems stated above.

rockfordlhotka commented 8 years ago

Fwiw, you are actually doing integration testing not unit testing - not useful, but more accurate :smile:

My previous statement remains accurate, in order to mock your DAL you need to interact with your DAL in a mockable way.

The issue isn't actually DbContext from Csla.Data - all it does is store the EF context object so it is available on the current thread. What you really need to mock is the EF context object itself, which I would think is quite difficult.

What I tend to do is what I show in the 'Using CSLA 4: Data Access' book, where I abstract the DAL so it consumes/returns DTOs - thus allowing you to easily replace your concrete DAL that uses EF with a concrete DAL that uses some mock data store.

JasonBock commented 8 years ago

I've written CSLA code where I inject a EF context interface via the techniques in my article, and it's very easy to write unit tests that use in memory DbSet objects. So this is definitely possible. I would personally not use DbContext directly in your BO; rather, use a way to inject that dependency and in the container configure it to use DbContext to return the implementation.