在單元測試使用指定 appsettings.json 或 IOptions

有時候寫到 WebService 的單元測試時,需要注入 IConfiguration 時該怎麼處理?
想要使用單元測試專用的 appsettings.json 要怎麼讀取?
甚至要注入 IOptions 時又要怎麼處理?
這篇文章就來告訴你該怎麼做。

前置作業

首先先在測試專案建立一個 appsettings.Test.json,命名可以依照需求改變,裡面的內容依照需求自行加入,或者直接把 WebService 專案裡的 appsettings.json 整個複製過來再改內容。

然後將它的 CopyToOutputDirectory 設為 Alaways。這點很重要,不然這個檔案不會出現在編譯後的資料夾中,執行時找不到檔案會報錯。

appsettings.Test.json

1
2
3
4
5
6
7
8
{
"MySection": "FooBar",
"YourSettingKey": "MyValue",
"MyObject": {
"Foo": "Bar",
"Sample": "Text"
}
}

指定 appsettings

接下來再建立一個 AppSettingProvider.cs,主要提供 Appsettings 的內容。

AppSettingProvider.cs

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
31
32
33
34
35
using System.IO;
using Microsoft.Extensions.Configuration;

namespace WebApiTests.TestUtilities;

public static class AppSettingProvider
{
/// <summary>
/// 取得測試的AppSettings
/// </summary>
/// <returns></returns>
public static IConfiguration GetTestAppSettings()
{
// 組合路徑,我放在專案目錄下 Settings 的資料夾裡
var pah = Path.Combine("Settings", "appsettings.Test.json");
var builder = new ConfigurationBuilder().AddJsonFile(pah);

var config = builder.Build();
return config;
}

// 後面可以在讀取各種設定,例如
/// <summary>
/// 取得開發的 AppSettings
/// </summary>
/// <returns></returns>
public static IConfiguration GetTestAppSettings()
{
var pah = Path.Combine("Settings", "appsettings.Development.json");
var builder = new ConfigurationBuilder().AddJsonFile(pah);

var config = builder.Build();
return config;
}
}

之後就可以在測試中指定設定檔和直接使用 IConfiguration。

ControllerTests.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ControllerTests
{
privare readonly IConfiguration _config;

public ControllerTests()
{
_config = AppSettingProvider.GetTestAppSettings();
}

[Fact]
public void GetMySection_從appsettings取值_應回傳FooBar()
{
// arrange
var expected = "FooBar";

// act
var actual = _config.GetValue<string>("MySection");

// assert
Assert.Contains(expected, actual);
}
}

IOptions

接下來我們來看,測試遇到使用 IOptions 時該如何處理。

先來看看我們的 OptionsModel。

MyObjectOptions.cs

1
2
3
4
5
6
7
8
public class MyObjectOptions
{
public static readonly string SectionName = "MyObject";

public string Foo { get; set; }

public string Sample { get; set; }
}

看來是要指定一個名為 MyObject 的 section。那就來建立一個 AppSettingProvider 提供一下。

AppSettingProvider.cs

1
2
3
4
5
6
7
8
9
10
11
12
public static class AppSettingProvider
{
public static IOptions<MyObjectOptions> GetMyObjectOptions()
{
var testAppSettings = Path.Combine("Settings", "appsettings.Test.json");
var builder = new ConfigurationBuilder().AddJsonFile(testAppSettings);
var config = builder.Build();

var options = Options.Create(config.GetSection(MyObjectOptions.SectionName).Get<MyObjectOptions>());
return options;
}
}

之後就可以在測試中直接使用 IOptions 了。

ControllerTests.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ControllerTests
{
privare readonly IOptions<MyObjectOptions> _options;

public ControllerTests()
{
_options = AppSettingProvider.GetMyObjectOptions();
}

[Fact]
public void GetSample_從appsettings取值_應回傳Text()
{
// arrange
var expected = "Text";

// act
var actual = _options.Value.Sample;

// assert
Assert.Contains(expected, actual);
}
}

總結

其實就只是由 DI 操作改為手動載入,IOptions 的話沒有變。

在寫 dotnet core 的時候還是別忘了 IConfiguration 的操作,還有 IOptions 的運用。不然到時候像我一樣突然要手動載入就措手不及,開始 Google 個半天了。

參考

Populate IConfiguration for unit tests - stackoverflow

.NET Core Unit Testing - Mock IOptions - stackoverflow