Asp.Net Core 2.0 Web Api Unit Testing

There exist many kind of tests: unit tests, integration tests, acceptance test, UI tests.
The goal of this tutorial is to show how to write unit tests that gives us  a better return on test investment. 

UNIT TEST: Unit testing test behavior of a class and drive its implementation, run in memory and mocks the Data Access Layer 

INTEGRATION TEST: test the entire system and focus on interactions between many classes, uses the real database and test the Data Access Layer and its value is to catch regressions 

SUBCUTANEOUS TEST:  test that operates just under the UI of an application. This is particularly valuable when doing functional testing of an application: when you want to test end-to-end behavior, but it’s difficult to test through the UI itself. Martin Fowler ( https://martinfowler.com/bliki/SubcutaneousTest.html) 

UI TEST: tests the user interface and verifies that the entire application is working properly 

Let’s consider the following testing pyramid 

TestingPyramid

The further we go to the top of the pyramid 

  • tests are fewer 
  • the tests are less stable
  • More complex 
  • Narrower scope 
  • Slower in execution time 

The further we go down the pyramid 

  • Less we have tests 
  • the tests are more stable,
  • Less complex 
  • Broader reach 
  • Faster in execution times 

Let us consider the following project dependencies :

For the goal of this tutorial, I will not write unit test of all my projects even if all projects must be unit tested but I will proceed as follow: 

  • Unit Test my business Projet: LogCorner.BlogPost.Business 
  • Integration Test of my WebAPI project: LogCorner.BlogPost.Api 
  • UI Test of my web application : LogCorner.BlogPost.Mvc 

Lets talk about Unit Testing TDD or BDD? 

BDD approach: in absolute terms, TDD plus natural language  expression using Gherkin. 

Using TDD approach, we must write the tests first using the red-green-refactor cycle. 

RED: Write the test first, it will fail 

GREEN: Write tested code as simple as possible to make it pass 

REFACTOR:  Refactor your code; your test may fail, so make it pass again 

But if you have already a production code, how to test it? 

 public class BlogService : IBlogService
    {
        private readonly IUnitOfWorkCore<LogcornerBlogpostContext> _unitOfWork;

        public BlogService(IUnitOfWorkCore<LogcornerBlogpostContext> unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public async Task UpdateAsync(Blog blog, string email)
        {
            if (blog == null || string.IsNullOrWhiteSpace(email))
            {
                throw new ArgumentNullException();
            }
            var old = await GetAsync(blog.BlogId);

            if (old == null)
            {
                throw new Exception($"blog : {blog.BlogId} must existe in database");
            }
            if (old.Owner != email)
            {
                throw new AuthenticationException($"You are not authorized to update the blog of someone else : {blog.BlogId}");
            }

            old.Url = blog.Url;
            old.Description = blog.Description;

            _unitOfWork.GetRepository<Blog>().Update(old);
            await _unitOfWork.SaveAsync();
        }

        public async Task CreateAsync(Blog blog, string owner)
        {
            if (blog == null || string.IsNullOrWhiteSpace(owner))
            {
                throw new ArgumentNullException();
            }

            blog.Owner = owner;
            _unitOfWork.GetRepository<Blog>().Create(blog);
            await _unitOfWork.SaveAsync();
        }

        public async Task<List<Blog>> GetAsync()
        {
            var result = await _unitOfWork.GetRepository<Blog>().All().ToListAsync();
            return result;
        }

        public async Task<Blog> GetAsync(int blogId)
        {
            return await _unitOfWork.GetRepository<Blog>().GetByIdAsync(blogId);
        }

        public async Task<bool> OwnedByAsync(int id, string owner)
        {
            var blog = await _unitOfWork.GetRepository<Blog>().GetByIdAsync(id);
            if (blog == null)
            {
                return false;
            }

            return blog.Owner == owner;
        }

        public async Task DeleteAsync(int blogId, string email)
        {
            if (blogId <= 0)
            {
                throw new ArgumentNullException(nameof(blogId), $"blog : {blogId} cannot be null");
            }

            if (string.IsNullOrWhiteSpace(email))
            {
                throw new ArgumentNullException(nameof(email), $"email : {email} cannot be null");
            }

            var old = await GetAsync(blogId);
            if (old == null)
            {
                throw new Exception($"blog : {blogId} cannot be null");
            }
            if (old.Owner != email)
            {
                throw new AuthenticationException($"You are authorized to update the blog : {blogId}");
            }
            _unitOfWork.GetRepository<Blog>().Delete(blogId);
            await _unitOfWork.SaveAsync();
        }
    }

Given the previous class, I would like to unit test my UpdateAsync method. 

To update a blog post, I must verify this: 

  • You must provide a non nullable blog post objet and your email 
  • The blog post , you want to update must exist on database 
  • You must be owner of the blog post you want to update 
  • The system must replace blog post description and url, with the values provider by the user 
  • The system must call GetRepository<Blog>().Update(old) to perform update 
  • The system must call SaveAsync() to save updated blog post 

TEST 1 : To update a blog post, the user must provide a non nullable blog post objet and his email adress

Here, I verify, if the user provide a null blog or an empty or null email address then an ArgumentNullException is thrown

 [Theory(DisplayName = "To update a blog post you must provide a valid blog object and a userName")]
        [ClassData(typeof(BadRequestBlogTestData))]
        public async Task UpdateBlogTestWitheInvalidParameters(Blog blog, string userName)
        {
            await Assert.ThrowsAsync<ArgumentNullException>(() => _blogService.UpdateAsync(blog, userName));
        }
 public class BadRequestBlogTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { null, null };
            yield return new object[] { null, string.Empty };
            yield return new object[] { null, "jean.dupont" };
            yield return new object[] { new Blog(), null };
            yield return new object[] { new Blog(), string.Empty };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

TEST2:  The blog post , you want to update must exist on database 

I want to test the following:  

I call GetAsync to retrieve the blog post by Id and I verify if the result is null or not 

As you can see  GetAsync make a call to _unitOfWork.GetRepository<Blog>().GetByIdAsync(blogId); 

So, I must mock the call of  GetByIdAsync(blogId); to return an existing blogpost. 

Here I mock IUnitOfWorkCore<LogcornerBlogpostContext> because my blogService need it and I don’t want to use the real implementation of my repository. 

Do not mock unitOfWork, means I’m doing integration testing but not unit testing. 

Let’s go ahead and write the test 

 [Theory(DisplayName = "To update a blog post you must provide an existing blogId")]
        [ClassData(typeof(UpdateBlogTestData))]
        public async Task UpdateBlogTestWithNoExistingBlogThrowsException(Blog blog, string userName)
        {
            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().GetByIdAsync(It.IsAny<int>())).Returns(Task.FromResult((Blog)null)).Verifiable();

            await Assert.ThrowsAsync<Exception>(() => _blogService.UpdateAsync(blog, userName));
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().GetByIdAsync(blog.BlogId));
        }
 public class UpdateBlogTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new Blog
            {
                BlogId = 0,
                Url = "http://www.jean.dupont.com",
                Description = "lorem"
            }, "jean.dupont" };
            yield return new object[] { new Blog
            {
                BlogId = -1,
                Url = "http://www.jean.dupont.com",
                Description = "lorem"
            }, "jean.dupont" };
            yield return new object[] { new Blog
            {
                BlogId = 18,
                Url = "http://www.jean.dupont.com",
                Description = "lorem"
            }, "jean.dupont" };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

I verify, if the blog does not exist in database,  then an Exception is thrown

TEST 3 : To update a blog post , you must be owner of that blog post : you cannot update a blog post of someone else 

So, I want to test the following:  

&nbsp;if&nbsp;(old.Owner&nbsp;!= email)&nbsp;
{&nbsp;
        throw&nbsp;new&nbsp;AuthenticationException($"You are not authorized to update the blog of someone else : {blog.BlogId}");&nbsp;
}

I test if the owner field of the blog I want to update is different from the current user email, if so the system throws an exception. 

Let’s go ahead and test it 

        [Theory(DisplayName = "When No Owner Of Blog Update Blog Must Throws Authentication Exception")]
        [ClassData(typeof(UpdateBlogNotOwnerTestData))]
        public async Task WhenNoOwnerOfBlogUpdateBlogMustThrowsAuthenticationException(Blog blog, string userName)
        {
            var outputBlog = new Blog
            {
                BlogId = 1,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner = "mathias.fererra"
            };

            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().GetByIdAsync(It.IsAny<int>())).Returns(Task.FromResult(outputBlog));
            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().Update(blog)).Verifiable();

            await Assert.ThrowsAsync<AuthenticationException>(() => _blogService.UpdateAsync(blog, userName));
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().GetByIdAsync(blog.BlogId));
            Assert.NotEqual(blog.Owner, userName);
        }
public class UpdateBlogNotOwnerTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new Blog
            {
                BlogId = 1,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner = "mathias.fererra"
            }, "jean.dupont"};
            yield return new object[] { new Blog
            {
                BlogId = 2,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner ="yves.kerghal"
            }, "jean.dupont" };
            yield return new object[] { new Blog
            {
                BlogId = 3,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner ="marc.laplace"
            },"jean.dupont" };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

TEST4: To update a blog post, you must set the url and description of the blog post you want to update with new values of url and description 

In this test I would like to verify the following 

old.Url = blog.Url; 

old.Description = blog.Description; 

        [Theory(DisplayName = "When owner of a given Blog update it with new values of URL and Description then the URL and Description of the blog must be ovverriden with new values")]
        [ClassData(typeof(UpdateBlogOwnerTestData))]
        public async Task WhenOwnerOfBlogUpdateBlogUrlAndDescriptionMustBeSetWithNewValues(Blog blog, string userName)
        {
            var blogToUpdate = new Blog
            {
                BlogId = 1,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner = "jean.dupont"
            };

            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().GetByIdAsync(It.IsAny<int>())).Returns(Task.FromResult(blogToUpdate)).Verifiable();
            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().Update(blog)).Verifiable();

            await _blogService.UpdateAsync(blog, userName);
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().GetByIdAsync(blog.BlogId), Times.Once);
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().Update(blogToUpdate), Times.Once);

            _unitOfWorkMock.Verify(m => m.GetRepository<Blog>().Update(It.Is<Blog>(n =>
                n.Url.Equals(blog.Url, StringComparison.InvariantCultureIgnoreCase)
                && n.Description.Equals(blog.Description, StringComparison.InvariantCultureIgnoreCase)
                && n.BlogId.Equals(blogToUpdate.BlogId)
                && n.Owner.Equals(blogToUpdate.Owner, StringComparison.InvariantCultureIgnoreCase)
            )));
        }
 public class UpdateBlogOwnerTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[]
            {
                new Blog
                {
                    BlogId = 1,
                    Url = "http://www.new1.jean.dupont.com",
                    Description = "new lorem 1",
                    Owner = "jean.dupont"
                },
                "jean.dupont"
            };
            yield return new object[] {
                new Blog
                {
                    BlogId = 2,
                    Url = "http://www.new2.jean.dupont.com",
                    Description = "new lorem 2",
                    Owner ="jean.dupont"
                },
                "jean.dupont"
            };
            yield return new object[] {
                new Blog
                {
                    BlogId = 3,
                    Url = "http://www.new3.jean.dupont.com",
                    Description = "new lorem 3",
                    Owner ="jean.dupont"
                },
                "jean.dupont"
            };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

I use a Verify call to check if the url and the description of the old blog are updated with new values

 _unitOfWorkMock.Verify(m => m.GetRepository<Blog>().Update(It.Is<Blog>(n =>
                n.Url.Equals(blog.Url, StringComparison.InvariantCultureIgnoreCase)
                && n.Description.Equals(blog.Description, StringComparison.InvariantCultureIgnoreCase)
                && n.BlogId.Equals(blogToUpdate.BlogId)
                && n.Owner.Equals(blogToUpdate.Owner, StringComparison.InvariantCultureIgnoreCase)
            )));

TEST5 : When the owner of a given blog try to uptade it , then the blog must be updated 

[Theory(DisplayName = "When owner of a given blog try to update it then the corresponding Blog is successfully updated")]
        [ClassData(typeof(UpdateBlogOwnerTestData))]
        public async Task WhenOwnerOfBlogUpdateBlogThenTheCorrespondingBlogIsUpdated(Blog blog, string userName)
        {
            var blogToUpdate = new Blog
            {
                BlogId = 1,
                Url = "http://www.jean.dupont.com",
                Description = "lorem",
                Owner = "jean.dupont"
            };

            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().GetByIdAsync(It.IsAny<int>())).Returns(Task.FromResult(blogToUpdate)).Verifiable();
            _unitOfWorkMock.Setup(r => r.GetRepository<Blog>().Update(blog)).Verifiable();

            await _blogService.UpdateAsync(blog, userName);
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().GetByIdAsync(blog.BlogId));
            _unitOfWorkMock.Verify(r => r.GetRepository<Blog>().Update(blogToUpdate), Times.Once,
                "update must be called only once");
            _unitOfWorkMock.Verify(r => r.SaveAsync(), Times.Once, "update must be called only once");
        }
 public class UpdateBlogOwnerTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[]
            {
                new Blog
                {
                    BlogId = 1,
                    Url = "http://www.new1.jean.dupont.com",
                    Description = "new lorem 1",
                    Owner = "jean.dupont"
                },
                "jean.dupont"
            };
            yield return new object[] {
                new Blog
                {
                    BlogId = 2,
                    Url = "http://www.new2.jean.dupont.com",
                    Description = "new lorem 2",
                    Owner ="jean.dupont"
                },
                "jean.dupont"
            };
            yield return new object[] {
                new Blog
                {
                    BlogId = 3,
                    Url = "http://www.new3.jean.dupont.com",
                    Description = "new lorem 3",
                    Owner ="jean.dupont"
                },
                "jean.dupont"
            };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

Test if SaveAsync is called

 _unitOfWorkMock.Verify(r => r.SaveAsync(), Times.Once, "update must be called only once");

Regards

Gora LEYE

I'm a microsoft most valuable professional (MVP) .NET Architect and Technical Expert skills located in Paris (FRANCE). The purpose of this blog is mainly to post general .NET tips and tricks, www.masterconduite.com Gora LEYE

Support us

BMC logoBuy me a coffee