はじめに
この記事はsalesforceのapexのテストで依存性注入を使ってテストを書きやすくしたい人向けです。
用語の説明はしないので必要に応じて調べてください。
結論
テストで書き替えたい処理をoverride可能なクラスおよびメソッドに閉じ込めて、テストクラスでテストしたい内容に書き替えたMockをコンストラクタインジェクションすることで依存性を注入する
参考にした公式のタイトルからすでに「apexにおける効果的な依存性注入」なのでもうこれでいいんじゃないかな
https://developer.salesforce.com/blogs/2021/08/effective-dependency-injection-in-apex
依存性注入例: 今日の日付が特定の日の場合だけ特別な処理を行いたい
下記のように、今日が大晦日なら大晦日!と返し、そうでなければ「大晦日ではありません」と返すクラスおよびメソッドがあったとします。
public with sharing class Oomisoka {
// 今日の日付が大晦日なら「大晦日!」を返して
// 大晦日以外であれば「大晦日ではありません」を返す
public String getOomisoka() {
Date today = Date.today();
Date lastDay = Date.newInstance(today.year(), 12, 31);
if (today == lastDay) {
return '大晦日!';
}
return '大晦日ではありません';
}
}
これについてテストクラスを作成しました。
@isTest
public with sharing class Oomisoka_T {
public static testMethod void testOomisoka() {
String testTitle = '正常系: 大晦日であれば「大晦日!」が返ることを確認';
Oomisoka oomisoka = new Oomisoka();
String actual = oomisoka.getOomisoka();
String expected = '大晦日!';
Assert.areEqual(expected, actual, testTitle);
}
}
さて、このテストクラスを動かすと
- 実行した日が大晦日であれば、テストが通過できる
- 実行した日が大晦日以外であれば、テスト失敗する
という結果になります。
これは、getOomisokaメソッドの中の
Date today = Date.today();
が実行した日によって変わってしまうのが原因です。正しく動くのかを検証したいのは
if (today == lastDay) {
の部分なので、todayは固定値でテストをしたいですよね。ここで依存性注入を使用します。
Oomisokaクラスに依存性を注入できるようにする
公式によると依存性注入ではoverride可能なクラスをコンストラクタインジェクションするのが良さそうなのでまずはここからやっていきましょう。
まず、override可能はTodayGetterクラスをサブクラスとして定義しましょう。
overrideをできるようにするため、virtual句を追加します。
これでこのクラスのgetメソッドをoverrideできるようになります。
public virtual class TodayGetter {
public TodayGetter() {
}
public virtual Date get() {
return Date.today();
}
}
次に、このTodayGetterクラスをプロパティとして持たせて、コンストラクタで設定できるようにします。
public with sharing class Oomisoka {
private TodayGetter todayGetter;
public Oomisoka(TodayGetter todayGetter) {
this.todayGetter = todayGetter ?? new TodayGetter();
}
最後にtodayの取得にこのプロパティを使うようにして完成です。
public with sharing class Oomisoka {
private TodayGetter todayGetter;
public Oomisoka(TodayGetter todayGetter) {
this.todayGetter = todayGetter ?? new TodayGetter();
}
// 今日の日付が大晦日なら「大晦日!」を返して
// 大晦日以外であれば「大晦日ではありません」を返す
public String getOomisoka() {
Date today = this.todayGetter.get();
Date lastDay = Date.newInstance(today.year(), 12, 31);
if (today == lastDay) {
return '大晦日!';
}
return '大晦日ではありません';
}
public virtual class TodayGetter {
public TodayGetter() {
}
public virtual Date get() {
return Date.today();
}
}
}
テストクラスからOomisokaクラスに依存性を注入する
テストクラスで依存性を注入する前に、テスト毎に値を切り替えてテストするためのサブクラスを作成しましょう。
コンストラクタで特定の日付を受け取り、書き替えたgetメソッドでその日付を利用するようにしています。
public class MockTodayGetter extends Oomisoka.TodayGetter {
private Date today;
public mockTodayGetter(Date today) {
this.today = today;
}
public override Date get() {
return this.today;
}
}
このサブクラスはTodayGetterを継承しているためOomisokaインスタンス生成時の引数として使用することができます。
Mockクラスを使うようにした完成系がこのようになります。
12/31を注入してちゃんと「大晦日!」が返るかというテストに仕上げます。
@isTest
public with sharing class Oomisoka_T {
public static testMethod void testOomisoka() {
String testTitle = '正常系: 大晦日であれば「大晦日!」が返ることを確認';
Date lastDay = Date.newInstance(2024, 12, 31);
MockTodayGetter mockTodayGetter = new MockTodayGetter(lastDay);
Oomisoka oomisoka = new Oomisoka(mockTodayGetter);
String actual = oomisoka.getOomisoka();
String expected = '大晦日!';
Assert.areEqual(expected, actual, testTitle);
}
public class MockTodayGetter extends Oomisoka.TodayGetter {
private Date today;
public mockTodayGetter(Date today) {
this.today = today;
}
public override Date get() {
return this.today;
}
}
}
依存性注入をしたテストの実行
これでOomisoka__tを実行すると、テストを通過しました。
実行日が12/5にも関わらず、大晦日であることのテストに成功しています。
依存性注入によってtodayの値を意図的に変えることに成功していることが分かりますね。
念の為、大晦日でないパターンのテストも追加しておきましょう。
7/1 であれば大晦日ではないので、「大晦日ではありません」という文言が返ることを確認します。
testTitle = '正常系: 大晦日以外であれば「大晦日ではありません」が返ることを確認';
lastDay = Date.newInstance(2024, 7, 1);
mockTodayGetter = new MockTodayGetter(lastDay);
oomisoka = new Oomisoka(mockTodayGetter);
actual = oomisoka.getOomisoka();
expected = '大晦日ではありません';
Assert.areEqual(expected, actual, testTitle);
これを追加しても、もちろんテストを通過します。このように、テスト毎に値を自由に変えることも可能なのが
この方法の特徴ですね。
想定される一番便利な使い方
今回はわかりやすい例としてtodayを書き換えるような実装にしましたが、一番便利な使い方としてはデータベースに関する部分になると思います。
例えば、「データベースから値をとってくる」という処理に対してテストを書こうとすると
- 必要なデータのインスタンスを生成する
- インスタンスをインサートする
- インサートしたデータを持って、テストの対象クラスで取得して処理をさせる
というめんどくささに加えて、インサートするときにバリデーションルールに引っ掛かりするとなおさら面倒になります。
結果、データのインスタンス生成部分だけで結構なコード量になってしまい、テストコードをどんどん書いていく気力も奪われてしまいます。
そこで、このデータベースから値を取得する部分をクラスに切り出して、テストクラスでは好きなインスタンスを返すようにoverrideしてしまえばデータベースの依存から切り離さすことができるのでとてもテストが書きやすくなります。
ただし、公式でも書いてあるとおりこれで完全にデータベース操作を伴う真の結合テストが行う責任が免除されるわけではないことには留意しておいてください。
Note that this doesn’t absolve us from the responsibility of having true integration tests with actual cross-object DML.