.Net Core TDD Databases
Note: This is using .net core 2.1
Using XUnit, but the concepts will remain the same no matter what testing framework you use.
Using Moq:
https://github.com/Moq/moq4/wiki/Quickstart
https://documentation.help/Moq/
There is also a really great free moq course here:
https://www.udemy.com/moq-framework
Tip:
Become familiar with Dependency Injection and Inversion Control in code so you can mock behaviour in tests.
Then become familiar with mocking in tests and assert behaviour based on mocked data or methods.
Github repo:
You can see working code here
https://github.com/CariZa/XUnit-CRUD-Example
https://github.com/CariZa/XUnit-CRUD-Example/tree/master/CRUD_Tests
Database Mocking in .net core
Models
Types of tests you could write to test a model:
Test a model can be created by creating an instance and testing the fields have been added to the model instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [Fact] public void BookModel_Instantiates() { string book = "Harry Potter"; string author = "JK Rowling"; string isbn = "123234345"; Book bookInst = new Book() { Name = book, Author = author, ISBN = isbn }; Assert.Matches(bookInst.Name, book); Assert.Matches(bookInst.Author, author); Assert.Matches(bookInst.ISBN, isbn); // Check no validation errors Assert.False(ValidateModel(bookInst).Count > 0); } |
Validate using ValidateModel
Test validations for models using ValidateModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | [Fact] public void BookModel_RequiresNameField() { string author = "JK Rowling"; string isbn = "123234345"; Book bookInst = new Book() { Author = author, ISBN = isbn }; var invalidFields = ValidateModel(bookInst); // Validation errors should return Assert.True(invalidFields.Count > 0); } [Fact] public void BookModel_DoesNotRequireOtherFields() { string book = "Harry Potter"; Book bookInst = new Book() { Name = book }; var invalidFields = ValidateModel(bookInst); Assert.False(invalidFields.Count > 0); } |
Validation Helper:
Also use this Helper method for the validation checks:
1 2 3 4 5 6 7 8 9 | // Validation Helper private IList<ValidationResult> ValidateModel(object model) { var validationResults = new List<ValidationResult>(); var ctx = new ValidationContext(model, null, null); Validator.TryValidateObject(model, ctx, validationResults, true); if (model is IValidatableObject) (model as IValidatableObject).Validate(ctx); return validationResults; } |
CRUD tests:
Following the 3 step approach: Arrange, Assert, Act.
Some tips:
You need a builder (DbContextOptionsBuilder), and a context. Your arrange will look something like this:
// Arrange
var builder = new DbContextOptionsBuilder
var context = new ApplicationDbContext(builder.Options);
Seed(context);
Create a Seed helper method:
1 2 3 4 5 6 7 8 9 10 11 12 | private void Seed(ApplicationDbContext context) { var books = new[] { new Book() { Name = "Name1", Author = "Author1", ISBN = "moo1", Id = 1}, new Book() { Name = "Name2", Author = "Author2", ISBN = "moo2", Id = 2}, new Book() { Name = "Name3", Author = "Author3", ISBN = "moo3", Id = 3} }; context.Books.AddRange(books); context.SaveChanges(); } |
Create a Teardown helper method:
1 2 3 4 5 6 | private async Task Teardown(ApplicationDbContext context) { var books = await context.Books.ToListAsync(); context.Books.RemoveRange(books); context.SaveChanges(); } |
Check you can Create, Update and Delete models from a database instance.
Using InMemoryDatabase:
Create example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | [Fact] public async System.Threading.Tasks.Task Create_OnPost_BookShouldBeAddedAsync() { // Arrange var builder = new DbContextOptionsBuilder<ApplicationDbContext>().UseInMemoryDatabase(databaseName: "InMemoryDb_Create"); var context = new ApplicationDbContext(builder.Options); Seed(context); // See above for this Helper Method // Act var model = new CreateModel(context); var book = new Book() { Name = "NameTest", ISBN = "ISBNTest", Author = "AuthorTest" }; await model.OnPost(book); // Assert var books = await context.Books.ToListAsync(); Assert.Equal(4, books.Count); Assert.Matches(books[3].Name, "NameTest"); } |
Read example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | [Fact] public async void Index_OnGet_BooksShouldSet() { // Arrange var builder = new DbContextOptionsBuilder<ApplicationDbContext>() .UseInMemoryDatabase(databaseName: "InMemoryDb_Index"); var mockAppDbContext = new ApplicationDbContext(builder.Options); Seed(mockAppDbContext); var pageModel = new IndexModel(mockAppDbContext); // Act await pageModel.OnGet(); // Assert var actualMessages = Assert.IsAssignableFrom<List<Book>>(pageModel.Books); Assert.Equal(3, actualMessages.Count); await Teardown(mockAppDbContext); } |
Update example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | [Fact] public async void Edit_OnGet_EditBookEntryIfValid() { // Arrange var builder = new DbContextOptionsBuilder<ApplicationDbContext>().UseInMemoryDatabase(databaseName: "InMemoryDb_Edit"); var context = new ApplicationDbContext(builder.Options); Seed(context); // Act var editPage = new EditModel(context); editPage.OnGet(2); editPage.Book.Author = "Test2"; editPage.Book.ISBN = "Test2"; editPage.Book.Name = "Test2"; await editPage.OnPost(); var books = await context.Books.ToListAsync(); // Assert Assert.Equal(editPage.Book, books[1]); Assert.Matches(books[1].Name, "Test2"); Assert.Matches(books[1].ISBN, "Test2"); Assert.Matches(books[1].Author, "Test2"); Assert.Matches(editPage.Message, "Book has been updated successfully"); await Teardown(context); } |
Delete example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | [Fact] public async void Index_OnPostDelete_BookGetsDeleted() { // Arrange var builder = new DbContextOptionsBuilder<ApplicationDbContext>() .UseInMemoryDatabase(databaseName: "InMemoryDb_Index"); var mockAppDbContext = new ApplicationDbContext(builder.Options); Seed(mockAppDbContext); var pageModel = new IndexModel(mockAppDbContext); // Act var deleteBooks = await mockAppDbContext.Books.ToListAsync(); await pageModel.OnPostDelete(deleteBooks[1].Id); var books = await mockAppDbContext.Books.ToListAsync(); // Assert Assert.Equal(2, books.Count); Assert.Matches(pageModel.Message, "Book deleted"); await Teardown(mockAppDbContext); } |