简体   繁体   中英

Creating repositories for Unit Testing

I've written a web application in C#, MVC, Entity-Frame, LINQ etc... and now I want to retroactively create Unit tests for the whole project.

I understand that in order to write effective Unit Tests, I need to create repositories for the models, so that they can be mocked, and thus entity framework will be do any hits on the database.

I have 3 main models; Category, Password, UserPassword.

Categories contain categories and passwords. And passwords contain UserPasswords (which link to User accounts). So I've created 3 repositories with the basic methods such as; Create, Delete, Update etc...

However, looking at this LINQ query:

var selectedCategoryItem = DatabaseContext.Categories
                .Where(c => c.CategoryId == ParentCategoryId)
                .Include(c => c.SubCategories)
                .Include(c => c.Passwords)
                .Include(c => c.Passwords.Select(p => p.Creator))
                .ToList()
                .Select(c => new Category()
                {

                SubCategories = c.SubCategories
                            .Where(sub => !sub.Deleted)
                            .OrderBy(sub => sub.CategoryOrder)
                            .ToList(),                          //make sure only undeleted subcategories are returned

                Passwords = c.Passwords
                            .Where(pass => !pass.Deleted
                            && (
                            (UserPasswordList.Any(up => up.PasswordId == pass.PasswordId && up.Id == UserId))
                            || (userIsAdmin && ApplicationSettings.Default.AdminsHaveAccessToAllPasswords)
                            || pass.Creator_Id == UserId
                            )
                            )   //make sure only undeleted passwords - that the current user has acccess to - are returned
                            .OrderBy(pass => pass.PasswordOrder)
                            .ToList(),

                CategoryId = c.CategoryId,
                CategoryName = c.CategoryName,
                Category_ParentID = c.Category_ParentID,
                CategoryOrder = c.CategoryOrder,
                Parent_Category = c.Parent_Category,
                Deleted = c.Deleted
                })
                .SingleOrDefault();

If the repositories just have basic methods like Save, Update, Delete, Insert, FindById etc.... how should I break the query up into the repositories? There's also a lot of business logic in there such as filtering for passwords that the user has access to, where should that sit?

I read something about returning IQueryable objects, so that LINQ queries can be changed in the service layer... how would that look?

If you want to write unit tests, all of that LINQ to Entities, should be behind an Interface,

eg

public interface ICategoryRepository {
    List<Category> GetCategoriesForParentId(int id);
}

then a class which implements that method and contains the LINQ and filtering logic.

Now in the client classes which need access to it, pass in an ICategoryRepository via the constructor (recommended). And for Unit Testing, pass in a mocked repository into the constructor which returns a pre-defined result set.

For testing the actual repository filtering/transforming logic, what i found best, was to use SQLite or SQL CE, and have an integration test, which loads up a pre-defined database, which is in a known state (either from a saved copy, or re-creating it each test) and have an integration tests, pointing the repository class at the SQLite or SQL CE database, rather than your real one so that is always in a known state, run the query, and check the result set.

EDIT:

Either pass in the details needed by the repository into the method, eg:

List<Category> GetCategoriesForParentIdAnduser(int id, int passwordId, int userId);

Or, as User is probably across the site, you could pass a

public interface IUserConfig {
     int PasswordId{ get; set; }
     int UserId { get; set;}
}


// Constructor for CategoryRepository : ICategoryRepository 
public CategoryRepository(DbContext context, IUserConfig)  
{
   ....
}

then

public List<Category> GetCategoriesForParentId(int id){
     return _context.Categories
                .....
                Passwords = c.Passwords.Where(
                            .....
                            (UserPasswordList.Any(up => up.PasswordId == _userConfig.PasswordId 
                                                     && up.Id == _userConfig.UserId))                            
                .....
}

EDIT2: Just noticed the .SingleOrDefault() , than rather than returning List<Category> you would return Category .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM