EF unit testing - how to extract EF code from BL?

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

Like unit test, business logic code that has Entity Framework code.

I have a WCF service with three layers:

  • Service level
  • Business logic level
  • Data access level

My business logic uses DbContext for all database operations. All my objects are now POCOs (usually this is an ObjectContext, but I changed that).

I read here and here for reasons why we should not mock \ fake DbContext.

He said: "That's why I believe that code related to the / Linq -to-entity context should be covered by integration tests and work with a real database."

and: "Of course, your approach works in some cases, but a module testing strategy must work in all cases - for it to work, you must completely move EF and IQueryable from your test method."

My question is: how do you achieve this?

public class TaskManager { public void UpdateTaskStatus( Guid loggedInUserId, Guid clientId, Guid taskId, Guid chosenOptionId, Boolean isTaskCompleted, String notes, Byte[] rowVersion ) { using (TransactionScope ts = new TransactionScope()) { using (CloseDBEntities entities = new CloseDBEntities()) { User currentUser = entities.Users.SingleOrDefault(us => us.Id == loggedInUserId); if (currentUser == null) throw new Exception("Logged user does not exist in the system."); // Locate the task that is attached to this client ClientTaskStatus taskStatus = entities.ClientTaskStatuses.SingleOrDefault(p => p.TaskId == taskId && p.Visit.ClientId == clientId); if (taskStatus == null) throw new Exception("Could not find this task for the client in the database."); if (taskStatus.Visit.CustomerRepId.HasValue == false) throw new Exception("No customer rep is assigned to the client yet."); TaskOption option = entities.TaskOptions.SingleOrDefault(op => op.Id == optionId); if (option == null) throw new Exception("The chosen option was not found in the database."); if (taskStatus.RowVersion != rowVersion) throw new Exception("The task was updated by someone else. Please refresh the information and try again."); taskStatus.ChosenOptionId = optionId; taskStatus.IsCompleted = isTaskCompleted; taskStatus.Notes = notes; // Save changes to database entities.SaveChanges(); } // Complete the transaction scope ts.Complete(); } } } 

In the above code, there is a demonstration of the function from my business logic. The function has several "trips" to the database. I don’t understand how exactly I can remove the EF code from this function in a separate assembly so that I can unit test this function (by entering some fake data instead of the EF data) and integrate the test assembly containing the "EF functions".

Can Ladislav or anyone else help?

[change]

Here is another example code from my business logic, I don’t understand how I can ā€œmove EF and IQueryable codeā€ from my test method:

 public List<UserDto> GetUsersByFilters( String ssn, List<Guid> orderIds, List<MaritalStatusEnum> maritalStatuses, String name, int age ) { using (MyProjEntities entities = new MyProjEntities()) { IQueryable<User> users = entities.Users; // Filter By SSN (check if the user ssn matches) if (String.IsNullOrEmusy(ssn) == false) users = users.Where(us => us.SSN == ssn); // Filter By Orders (check fi the user has all the orders in the list) if (orderIds != null) users = users.Where(us => UserContainsAllOrders(us, orderIds)); // Filter By Marital Status (check if the user has a marital status that is in the filter list) if (maritalStatuses != null) users = users.Where(pt => maritalStatuses.Contains((MaritalStatusEnum)us.MaritalStatus)); // Filter By Name (check if the user name matches) if (String.IsNullOrEmusy(name) == false) users = users.Where(us => us.name == name); // Filter By Age (check if the user age matches) if (age > 0) users = users.Where(us => us.Age == age); return users.ToList(); } } private Boolean UserContainsAllOrders(User user, List<Guid> orderIds) { return orderIds.All(orderId => user.Orders.Any(order => order.Id == orderId)); } 
+2
unit-testing entity-framework
Jun 07 2018-12-12T00:
source share
2 answers

If you want to unit test your TaskManager class, you must use the repository descriptor template and enter repositories such as UserRepository or ClientTaskStatusRepository in this class. Then, instead of creating a CloseDBEntities object CloseDBEntities you will use these repositories and call their methods, for example:

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

If you want to use the integration test of your TaskManager class, the solution is much simpler. You just need to initialize the CloseDBEntities object with a connection string pointing to the test database, and what it is. One way to achieve this is to insert the CloseDBEntities object in the TaskManager class.

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

+4
Jun 08 2018-12-12T00:
source share

There are a few misunderstandings here.

First: Repository Template . This is not just a facade over DbSet for unit testing! A repository is a pattenr strongly associated with the concepts of Aggregate and Aggreate Root Driven Driven Design . A collection is a collection of related objects that must remain consistent with each other. I mean business consistency, not just the validity of foreign keys. For example: a customer who has made 2 orders should receive a 5% discount. Thus, we must somehow manage the consistency between the number of order objects related to the client’s object and the discount property of the client’s object. A node responsible for this is the aggregate root. It is also the only node that should be accessible directly from outside the aggregate. And the repository is a utility for getting the aggregate root from some (possibly permanent) storage.

A typical use case is to create UoW / Transaction / DbContext / WhateverYouNameIt, get one aggregate root object from the repository, call some methods on it or access some other objects by moving from the root, Commit / SaveChanges / It doesn't matter. See how different it is from yur samples.

Second: Business logic . I have already shown you one example: a customer who has made 2 orders should receive a 5% discount. On the contrary: your second code sample is not business logic. This is just a request . The responsibility of this code is to retrieve some data from the repository . Storage technology is important in this case. . Therefore, I would recommend integration tests here instead of pretending that the repository does not matter when interacting with the repository is the sole purpose of this function.

I would also encapsulate this in a Request Object , which has already been proposed. Then - such a request object could be ridiculed. Behind this is not only DbContext. The whole QO.

The first code example is slightly better, because it probably entails some business logic, but it's hard to identify. This leads us to the third problem.

Third: Anemic domain model . Your domain does not look very object oriented. You have dumb entities and transaction scenarios over them. With 7 parameters! This is purely procedural programming.

Also, in your example of using UpdateTaskStatus - what is an aggregated root? Beffe, you answer this question, the most important question: what exactly do you want to do? Is this ... hmm ... marking the current task of the user when he was visited? What could be the Visit () method inside the Customer object? And this method should have something like this. CurrentTaskStatus.IsCompleted = true? It was just a random guess. If I missed, this would clearly show another problem. The domain model should use the ubiquitous language - something common to the programmer and the business. Your code does not have the expressive power that a common language gives. I just don’t know what is going on in UpdateTaskStatus with 7 parameters.

If you put the right expressive methods for doing business operations in your entities, which will also force you to not use DbContext at all, since you need your entities to remain constant. Then the mockery problem disappears. You can test the pure business logic without saving problems.

So, the last word: first revise your model. Make your API expressive by first using the ubiquitous language.

PS: Please do not treat me as a power. Maybe I'm completely wrong because I'm just starting to learn DDD.

+4
Jun 18 2018-12-18T00:
source share



All Articles