Posted at

Apex Stub APIを使って管理パッケージ内のglobalクラスを使ったメソッドのテストを書く

More than 1 year has passed since last update.


はじめに

AppExchangeアプリケーションではユーザがカスタマイズできるようにApexクラスをglobalで公開していることがあります。

例えば、ZuoraのOrder BuilderはZuoraのSOAP APIのApex SDKです。

大変便利なのですが、これを使ったクラスのテストはどう書くのでしょうか?

ドキュメントにも(SeeAllData=true)つけろとしか書いていないし、コミュニティを検索してもこんな解決方法しか見つかりません。これはテストしてるって言いません。


Apex Stub API

ApexでもSpring'17からスタブが使えるようになったので、これで解決してみました。



テスト対象クラス

こんなZuoraにクエリを投げるだけの処理のテストコードを書いてみましょう。

ZuoraApiWrapperクラス(後述)のインスタンスをテスト中はzApiStubというstatic変数を使うようにしています。


ZuoraExample.cls

public with sharing class ZuoraExample {

@TestVisible
private static ZuoraApiWrapper zApiStub;

public static String getBankTransferId() {
// テスト内ではzApiStubを利用する
ZuoraApiWrapper zApiInstance = Test.isRunningTest() ? zApiStub : new ZuoraApiWrapper();
zApiInstance.zlogin();
String zoql = 'SELECT Id FROM PaymentMethod WHERE Type=\'BankTransfer\' AND Active = true';
List<Zuora.zObject> methods = zApiInstance.zquery(zoql);
if (methods.isEmpty()) {
throw new NoDataFoundException('ZuoraBankTransferPaymentMethodが見つかりません。');
}
return (String) methods.get(0).getValue('Id');
}
}



スタブが作成できない問題の回避のためのクラス

管理パッケージ内のクラスであるZuora.zApiはスタブを作成できないらしく素直にZuora.zApiのスタブを作ろうとするとエラーになります。なので、仕方なくラッパークラスを作ることで回避します。


ZuoraApiWrapper.cls

public class ZuoraApiWrapper {

private Zuora.zApi api;

public ZuoraApiWrapper() {
api = new Zuora.zApi();
}

public Zuora.zApi.LoginResult zlogin() {
return api.zlogin();
}

public List<Zuora.zObject> zquery(String query) {
return api.zquery(query);
}
}



テストコード

テストコードはStubProviderを使ってこんな風に書けます。

もうちょっと頑張れば引数がこれだったらこれを返すとか2回目の呼び出しだったらこれを返すみたいにも書けるようにできると思います。そのレベルのものも標準クラスで提供して欲しいものですが…


ZuoraExampleTest.cls

@isTest

private class ZuoraExampleTest {
/**
* 呼ばれたメソッドの情報
*/

class MethodInformation {
Object stubbedObject{get;set;}
String stubbedMethodName{get;set;}
Type returnType{get;set;}
List<Type> listOfParamTypes{get;set;}
List<String> listOfParamNames{get;set;}
List<Object> listOfArgs{get;set;}
}

/**
* Stub APIの実装
* https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_testing_stub_api.htm
*/

class ZApiStubProvider implements System.StubProvider {
Map<String, MethodInformation> calledMethod = new Map<String, MethodInformation>();
Map<String, Object> returnMap = new Map<String, Object>();
Map<String, Exception> exceptionMap = new Map<String, Exception>();

public Object handleMethodCall(
Object stubbedObject,
String stubbedMethodName,
Type returnType,
List<Type> listOfParamTypes,
List<String> listOfParamNames,
List<Object> listOfArgs
) {
MethodInformation method = new MethodInformation();
method.stubbedObject = stubbedObject;
method.stubbedMethodName = stubbedMethodName;
method.returnType = returnType;
method.listOfParamTypes = listOfParamTypes;
method.listOfParamNames = listOfParamNames;
method.listOfArgs = listOfArgs;

calledMethod.put(stubbedMethodName, method);

Object res = returnMap.get(stubbedMethodName);
if (res != null)
return res;
Exception e = exceptionMap.get(stubbedMethodName);
if (e != null)
throw e;
return null;
}

ZuoraApiWrapper createStub() {
return (ZuoraApiWrapper) Test.createStub(ZuoraApiWrapper.class, this);
}

/**
* メソッドの戻り値を設定
*/

void setReturnValue(String methodName, Object res) {
returnMap.put(methodName, res);
}

/**
* メソッドが投げるExceptionを設定
*/

void setException(String methodName, Exception e) {
exceptionMap.put(methodName, e);
}

/**
* 呼ばれたメソッドの情報を取得。アサーションに使用する。
*/

MethodInformation called(String methodName) {
return calledMethod.get(methodName);
}
}

/**
* 正常時
*/

@isTest(SeeAllData=true)
static void testSuccess() {
ZApiStubProvider provider = new ZApiStubProvider();
// zqueryメソッドで正しい戻り値を返すよう設定
Zuora.zObject paymentMethod = new Zuora.zObject('PaymentMethod');
paymentMethod.setValue('Id', 'BT0001');
provider.setReturnValue('zquery', new List<Zuora.zObject>{paymentMethod});
// スタブを使うよう設定
ZuoraExample.zApiStub = provider.createStub();

String bankId = ZuoraExample.getBankTransferId();

// zqueryが呼ばれること
System.assertNotEquals(null, provider.called('zquery'));
// 戻り値が正しいこと
System.assertEquals('BT0001', bankId);
}

/**
* 0件の場合
*/

@isTest(SeeAllData=true)
static void testNoData() {
ZApiStubProvider provider = new ZApiStubProvider();
// zqueryメソッドで空のリストを返すよう設定
provider.setReturnValue('zquery', new List<Zuora.zObject>{});
// スタブを使うよう設定
ZuoraExample.zApiStub = provider.createStub();

Exception ex;
try {
ZuoraExample.getBankTransferId();
} catch (NoDataFoundException e) {
ex = e;
}

// zqueryが呼ばれること
System.assertNotEquals(null, provider.called('zquery'));
// NoDataFoundExceptionが発生すること
System.assertEquals('ZuoraBankTransferPaymentMethodが見つかりません。', ex.getMessage());
}
}



問題


  • ZuoraApiWrapperのカバレッジが0%になる。


    • ここでは書きませんが、この問題はうまいこと回避してください。




終わりに

Apexではテストのしようがないみたいなこともけっこうあったので、そんなときにStub APIはなかなか使えそうです。


参照