iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 21
0
Microsoft Azure

Azure 的自我修煉系列 第 21

Day 21 實作 Razor ASP.NET Core 中的頁面單元測試

實作Razor ASP.NET Core 中的頁面單元測試

延續 Day09 實作官網 ASP.NET Core 教學我們把專案加入 xUnit Test

測試架構為 xUnit。 物件模擬架構為 Moq。

創建測試專案

在專案目錄下加入 tests 資料夾,增加 xUnit 專案

dotnet new xunit -o Tests

安裝套件

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.AspNetCore.TestHost
dotnet add package Moq
dotnet add package Newtonsoft.Json
dotnet add package System.Diagnostics.TraceSource
dotnet add package System.Net.Http

增加參考

dotnet add ./Tests.csproj reference ../PellokITHome.csproj  

Utilities class

創建 Utilities

mkdir Utilities
touch Utilities/Utilities.cs

Utilities.cs的內容如下:

using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PellokITHome.Data;

namespace Tests
{
    public static class Utilities
    {
        #region snippet1
        public static DbContextOptions<PellokITHomeContext> TestDbContextOptions()
        {
            // Create a new service provider to create a new in-memory database.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            // Create a new options instance using an in-memory database and 
            // IServiceProvider that the context should resolve all of its 
            // services from.
            var builder = new DbContextOptionsBuilder<PellokITHomeContext>()
                .UseInMemoryDatabase("InMemoryDb")
                .UseInternalServiceProvider(serviceProvider);

            return builder.Options;
        }
        #endregion
    }
}

DataAccessLayerTest.cs

創建資料測試

mkdir UnitTests
touch UnitTests/DataAccessLayerTest.cs

內容如下:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;
using PellokITHome.Data;
using PellokITHome.Models;

namespace Tests.UnitTests
{
    public class DataAccessLayerTest
    {
        [Fact]
        public async Task GetMessagesAsync_MessagesAreReturned()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();

                // Act
                var result = await db.GetArticlesAsync();

                // Assert
                var actualMessages = Assert.IsAssignableFrom<List<Article>>(result);
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }

        [Fact]
        public async Task AddMessageAsync_MessageIsAdded()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var recId = 10;
                var expectedMessage = new Article() { ID = recId, Title = "Message" };

                // Act
                await db.AddArticleAsync(expectedMessage);

                // Assert
                var actualMessage = await db.FindAsync<Article>(recId);
                Assert.Equal(expectedMessage, actualMessage);
            }
        }

        [Fact]
        public async Task DeleteAllMessagesAsync_MessagesAreDeleted()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();

                // Act
                await db.DeleteAllArticlesAsync();

                // Assert
                Assert.Empty(await db.Articles.AsNoTracking().ToListAsync());
            }
        }

        [Fact]
        public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                #region snippet1
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();
                var recId = 1;
                var expectedMessages = 
                    seedMessages.Where(message => message.ID != recId).ToList();
                #endregion

                #region snippet2
                // Act
                await db.DeleteArticleAsync(recId);
                #endregion

                #region snippet3
                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
                #endregion
            }
        }

        #region snippet4
        [Fact]
        public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();
                var recId = 4;

                // Act
                try
                {
                    await db.DeleteArticleAsync(recId);
                }
                catch
                {
                    // recId doesn't exist
                }

                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }
        #endregion
    }
}

修改PellokITHome專案

修改PellokITHomeContext.cs檔案

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PellokITHome.Models;

namespace PellokITHome.Data
{
    public class PellokITHomeContext : DbContext
    {
        public PellokITHomeContext (
            DbContextOptions<PellokITHomeContext> options)
            : base(options)
        {
        }

        public DbSet<PellokITHome.Models.Article> Articles { get; set; }

        #region snippet1
        public async virtual Task<List<Article>> GetArticlesAsync()
        {
            return await Articles
                .OrderBy(Article => Article.Title)
                .AsNoTracking()
                .ToListAsync();
        }
        #endregion

        #region snippet2
        public async virtual Task AddArticleAsync(Article Article)
        {
            await Articles.AddAsync(Article);
            await SaveChangesAsync();
        }
        #endregion

        #region snippet3
        public async virtual Task DeleteAllArticlesAsync()
        {
            foreach (Article Article in Articles)
            {
                Articles.Remove(Article);
            }
            
            await SaveChangesAsync();
        }
        #endregion

        #region snippet4
        public async virtual Task DeleteArticleAsync(int id)
        {
            var Article = await Articles.FindAsync(id);

            if (Article != null)
            {
                Articles.Remove(Article);
                await SaveChangesAsync();
            }
        }
        #endregion

        public void Initialize()
        {
            Articles.AddRange(GetSeedingArticles());
            SaveChanges();
        }

        public static List<Article> GetSeedingArticles()
        {
            return new List<Article>()
            {
                new Article(){ Title = "Day01 Azure 的自我修煉" },
                new Article(){ Title = "Day02 申請Azure帳號" },
                new Article(){ Title = "Day03 Resource Group 資源群組" }
            };
        }
    }
}

修改以上內容後,需要重建DB,確認專案沒有問題,執行正常。

執行測試

dotnet test

https://ithelp.ithome.com.tw/upload/images/20200921/20072651DdJy1B0RiS.png

解決方案

合併兩個專案為一個解決方案

# 創建方案
dotnet new sln -o PellokITHomePipeline

# 到方案目錄下
cd PellokITHomePipeline

# 複製專案
cp -r ../PellokITHome .
# 移除專案暫存檔
rm -rf PellokITHome/.git
rm -rf PellokITHome/.vscode
rm -rf PellokITHome/obj
rm -rf PellokITHome/bin

# 複製專案
cp -r ../Tests .
# 移除專案暫存檔
rm -rf Tests/.git
rm -rf Tests/.vscode
rm -rf Tests/obj
rm -rf Tests/bin

# 加入專案到方案
dotnet sln add ./PellokITHome/PellokITHome.csproj
dotnet sln add ./Tests/Tests.csproj

https://ithelp.ithome.com.tw/upload/images/20200921/20072651wOiv2KRyZh.png

增加Git版控

新增 .gitignore

*/bin/*
*/obj/*
*/.vscode/*
*.db

提交索引、提交版本

mv PellokITHome/.gitignore .
git init
git status
git commit -m "first commit"

https://ithelp.ithome.com.tw/upload/images/20200921/20072651GMjMXpF9Kg.png

測試執行

code -r .

# 測試
dotnet test

相關連結:

上一篇 Day20 實作 Dotnet Test 測試範例
下一篇 Day22 整合CI測試到 Azure Pipeline 服務


上一篇
Day20 實作 Dotnet Test 測試範例
下一篇
Day22 整合CI測試到 Azure Pipeline 服務
系列文
Azure 的自我修煉30

尚未有邦友留言

立即登入留言