Unit testing EF - how to extract EF code out from BL?

前端 未结 2 1284
说谎
说谎 2020-12-16 08:34

I have read so much (dozens of posts) about one thing:

How to unit test business logic code that has Entity Framework code in it.

相关标签:
2条回答
  • 2020-12-16 08:54

    If you want to unit test your TaskManager class, you should employ the Repository dessign pattern and inject repositories such as UserRepository or ClientTaskStatusRepository into this class. Then instead of constructing CloseDBEntities object you will use these repositories and call their methods, for example:

    User currentUser = userRepository.GetUser(loggedInUserId);
    ClientTaskStatus taskStatus = 
        clientTaskStatusRepository.GetTaskStatus(taskId, clientId);
    

    If yout wanto to integration test your TaskManager class, the solution is much more simple. You just need to initialize CloseDBEntities object with a connection string pointing to the test database and that's it. One way how to achieve this is injecting the CloseDBEntities object into the TaskManager class.

    You will also need to re-create the test database before each integration test run and populate it with some test data. This can be achieved using Database Initializer.

    0 讨论(0)
  • 2020-12-16 09:10

    There are several misunderstandings here.

    First: The Repository Pattern. It's not just a facade over DbSet for unit testing! The repository is a pattenr strongly related to Aggregate and Aggreate Root concepts of Domain Driven Design. An aggregate is a set of related entities that should stay consistent to each other. I mean a business consistency, not just only a foreign keys validity. For example: a customer who have made 2 orders should get a 5% discount. So we should somehow manage the consistency between the number of order entities related to a customer entity and a discount property of the customer entity. A node responsible for this is an aggregate root. It is also the only node that should be accessible directly from outside of the aggregate. And the repository is an utility to obtain an aggregate root from some (maybe persistent) storage.

    A typical use case is to create a UoW/Transaction/DbContext/WhateverYouNameIt, obtain one aggregate root entity from the repository, call some methods on it or access some other entities by traversing from the root, Commit/SaveChanges/Whatever. Look, how far it differs from yur samples.

    Second: The Business Logic. I've already showed you one example: a customer who have made 2 orders should get a 5% discount. In contrary: your second code sample is not a business logic. It's just a query. The responsibility of this code is to obtain some data from the storage. In such a case, the storage technology behind it does matter. So I would recomend integration tests here rather than pretending the storage doesn't matter when interacting with the storage is the sole purpose of this function.

    I would also encapsulate that in a Query Object that was already suggested. Then - such a query object could be mocked. Not just DbContext behind it. The whole QO.

    The first code sample is a bit better because it probably ivolves some business logic, but that's dificult to identify. Wich leads us to the third problem.

    Third: Anemic Domain Model. Your domain doesnt' look very object oriented. You have some dumb entities and transaction scripts over them. With 7 parameters! Thats pure procedural programming.

    Moreover, in your UpdateTaskStatus use case - what is the aggregate root? Befere you answer that, the most important question first: what exactly do you want to do? Is that... hmm... marking a current task of a user done when he was visited? Than, maybe there should be a method Visit() inside a Customer Entity? And this method should have something like this.CurrentTaskStatus.IsCompleted = true? That was just a random guess. If I missed, that would clearly show another issue. The domain model should use the ubiquitous language - something common for the programmer and a business. Your code doesn't have that expressive power that a common language gives. I just don't know what is going on there in UpdateTaskStatus with 7 parameters.

    If you place proper expressive methods for performing business operations in your entities that will also enforce you to not use DbContext there at all, as you need your entities to stay persistence ignorant. Then the problem with mocking disappears. You can test the pure business logic without persistence concerns.

    So the final word: Reconsider your model first. Make your API expressive by using ubiquitous language first.

    PS: Please don't treat me as an authority. I may be completely wrong as I'm just starting to learn DDD.

    0 讨论(0)
提交回复
热议问题