.Net Core 2.1 TDD Database Requests

.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().UseInMemoryDatabase(databaseName: “InMemoryDb_Edit”);
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);
        }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.