Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。
今回は、Azure App Service上でAzure Functionsの関数を呼び出しているコントローラクラスと、Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテスト用プログラムを作成してみたので、共有する。
前提条件
下記記事の実装が完了していること。
また、プログラムの実行結果が以下の記事の通りであること。
結果として、SQLデータベース上のUSER_DATAテーブル、M_SEXテーブルには、以下のデータが入っていることになる。


作成したテスト用サンプルプログラム(App Service側)の内容
作成したサンプルプログラム(App Service側)の構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
pom.xmlは以下の通りで、Spring Boot 2.4.0でJUnit 4のテストクラスを利用するための設定を追加している。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demoAzureApp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demoAzureApp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombokの設定 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot 2.4.0でJUnit 4のテストクラスを利用するための設定 -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>1.12.0</version>
<configuration>
<schemaVersion>v2</schemaVersion>
<subscriptionId>(ログインユーザーのサブスクリプションID)</subscriptionId>
<resourceGroup>azureAppDemo</resourceGroup>
<appName>azureAppDemoService</appName>
<pricingTier>B1</pricingTier>
<region>japaneast</region>
<appServicePlanName>ASP-azureAppDemo-8679</appServicePlanName>
<appServicePlanResourceGroup>azureAppDemo</appServicePlanResourceGroup>
<runtime>
<os>Linux</os>
<javaVersion>Java 8</javaVersion>
<webContainer>Tomcat 8.5</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.war</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
</plugins>
</build>
</project>
また、テストクラスの内容は以下の通りで、MockMvcを使ってコントローラクラスのメソッド呼び出すと共に、RestTemplateクラスのメソッドをMock化して呼び出している。
package com.example.demo;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DemoControllerTest {
/**
* テスト対象のクラス
*/
@Autowired
private DemoController demoController;
/**
* テスト対象のクラス内で呼ばれるクラスのMockオブジェクト
*/
@Autowired
private RestTemplate restTemplate;
/**
* MockMvcオブジェクト
*/
private MockMvc mockMvc;
/**
* RestTemplateクラスのメソッドをMock化するためのサーバー
*/
private MockRestServiceServer mockServer;
/** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */
@Value("${demoAzureFunc.urlBase}")
private String demoAzureFuncBase;
/**
* 前処理(各テストケースを実行する前に行われる処理)
*/
@Before
public void init() {
// MockMvcオブジェクトにテスト対象メソッドを設定
mockMvc = MockMvcBuilders.standaloneSetup(demoController).build();
// テスト対象のクラスで使用するRestTemplateクラスのメソッドをMock化するためのサーバーを設定
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
}
@Test
public void testIndex() throws Exception {
// テスト対象メソッド(index)を実行
mockMvc.perform(get("/"))
// HTTPステータスがOKであることを確認
.andExpect(status().isOk())
// 次画面の遷移先がlist.htmlであることを確認
.andExpect(view().name("list"))
// Modelオブジェクトに検索Formが設定されていることを確認
.andExpect(model().attribute("searchForm", new SearchForm()))
// Modelオブジェクトにエラーが無いことを確認
.andExpect(model().hasNoErrors());
}
@Test
public void testSearch() throws Exception {
// Azure FunctionsのgetUserDataList関数を呼び出した結果をMock化
mockServer.expect(requestTo(demoAzureFuncBase + "getUserDataList"))
.andExpect(method(HttpMethod.POST)) // リクエストヘッダ内容の検証
.andExpect(content()
.string("{\"searchName\":[\"\"],\"searchSex\":[\"\"]}"))
.andRespond(withSuccess(makeGetUserDataListRes()
, MediaType.APPLICATION_JSON));
// テスト対象メソッド(search)を実行
mockMvc.perform(post("/search/")
// 検索条件のForm値を設定
.param("searchName", "")
.param("searchSex", ""))
// HTTPステータスがOKであることを確認
.andExpect(status().isOk())
// 次画面の遷移先がlist.htmlであることを確認
.andExpect(view().name("list"))
// Modelオブジェクトの検索Formに
// getUserDataList関数を呼び出した結果が設定されていることを確認
.andExpect(model().attribute("searchForm", makeSearchFormRes()))
// Modelオブジェクトにエラーが無いことを確認
.andExpect(model().hasNoErrors());
}
/**
* 返却されるユーザーデータリストを生成
* @return ユーザーデータリスト
*/
private ArrayList<UserData> makeUserDataList() {
ArrayList<UserData> userDataList = new ArrayList<>();
UserData userData = new UserData();
userData.setId("1");
userData.setName("テスト プリン");
userData.setBirthYear("2012");
userData.setBirthMonth("1");
userData.setBirthDay("15");
userData.setSex("女");
userDataList.add(userData);
userData = new UserData();
userData.setId("2");
userData.setName("テスト プリン2");
userData.setBirthYear("2013");
userData.setBirthMonth("2");
userData.setBirthDay("16");
userData.setSex("男");
userDataList.add(userData);
userData = new UserData();
userData.setId("3");
userData.setName("テスト プリン3");
userData.setBirthYear("2014");
userData.setBirthMonth("3");
userData.setBirthDay("17");
userData.setSex("女");
userDataList.add(userData);
return userDataList;
}
/**
* Azure FunctionsのgetUserDataList関数を呼び出した結果となる文字列を生成
* @return 生成した文字列
*/
private String makeGetUserDataListRes() {
String jsonResponseBody = null;
SearchResult searchResult = new SearchResult();
searchResult.setUserDataList(makeUserDataList());
try {
ObjectMapper objectMapper = new ObjectMapper();
jsonResponseBody = objectMapper.writeValueAsString(searchResult);
} catch (Exception ex) {
System.err.println(ex);
}
return jsonResponseBody;
}
/**
* Azure FunctionsのgetUserDataList関数を呼び出した結果となるFormオブジェクトを生成
* @return 生成したFormオブジェクト
*/
private SearchForm makeSearchFormRes() {
SearchForm resultSearchForm = new SearchForm();
resultSearchForm.setSearchName("");
resultSearchForm.setSearchSex("");
resultSearchForm.setUserDataList(makeUserDataList());
return resultSearchForm;
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-junit-test/demoAzureApp
作成したテスト用のサンプルプログラム(Azure Functions側)の内容
作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加したプログラムである。
<2021年4月13日 追記>
spring-cloud-function-dependenciesのバージョンは、2021年3月16日にリリースしたバージョン3.1.2を利用すると、1つのAzure Functions内に複数のファンクションを含む場合の不具合が解消できている。
その場合、Handlerクラスの継承するクラスを「AzureSpringBootRequestHandler」クラスから「FunctionInvoker」クラスに変更する。
spring-cloud-function-dependenciesの3.1.2を利用した実装サンプルは、以下の記事を参照のこと。
テストクラスの内容は以下の通りで、GetUserDataListHandlerクラスのexecuteメソッドを呼び出した結果を確認している。
package com.example;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import org.junit.Test;
import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler;
import com.example.model.SearchForm;
import com.example.model.SearchResult;
import com.example.mybatis.model.UserData;
public class GetUserDataListHandlerTest {
/**
* GetUserDataListHandlerクラスのexecuteメソッドをテストするメソッド
* @throws Exception
*/
@Test
public void executeTest() {
// AzureSpringBootRequestHandlerクラスのオブジェクトを生成し、
// GetUserDataListHandlerクラスのexecuteメソッドを実行
AzureSpringBootRequestHandler<SearchForm, SearchResult> handler
= new AzureSpringBootRequestHandler<>(DemoAzureFunction.class);
SearchResult result = handler.handleRequest(new SearchForm(), null);
// AzureSpringBootRequestHandlerクラスのオブジェクトをcloseするのを忘れず行う
handler.close();
// 取得内容をコンソールに表示
System.out.println("*** result.getUserDataList()の実行結果 ***");
for(UserData userData : result.getUserDataList()){
System.out.println(userData.toString());
}
System.out.println();
// 取得結果を確認
assertEquals(3, result.getUserDataList().size());
assertEquals(makeUserDataList().toString()
, result.getUserDataList().toString());
}
/**
* 返却されるユーザーデータリストを生成
* @return ユーザーデータリスト
*/
private ArrayList<UserData> makeUserDataList() {
ArrayList<UserData> userDataList = new ArrayList<>();
UserData userData = new UserData();
userData.setId("1");
userData.setName("テスト プリン");
userData.setBirthYear("2012");
userData.setBirthMonth("1");
userData.setBirthDay("15");
userData.setSex("女");
userDataList.add(userData);
userData = new UserData();
userData.setId("2");
userData.setName("テスト プリン2");
userData.setBirthYear("2013");
userData.setBirthMonth("2");
userData.setBirthDay("16");
userData.setSex("男");
userDataList.add(userData);
userData = new UserData();
userData.setId("3");
userData.setName("テスト プリン3");
userData.setBirthYear("2014");
userData.setBirthMonth("3");
userData.setBirthDay("17");
userData.setSex("女");
userDataList.add(userData);
return userDataList;
}
}なお、Azure Functions側のSpring Bootのバージョンは2.3.4.RELEASEなので、pom.xmlの修正は行っていない。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-junit-test/demoAzureFunc
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の通り。
1) App Serviceのコントローラのテストを実行した結果は以下の通り。なお、このとき、App Service側のSpring Bootの起動と、Azure Functionsを起動する「mvn azure-functions:run」というコマンドの実行は、共に行っていない。


2) Azure Functionsのテストを実行した結果は以下の通り。なお、このとき、Azure Functionsを起動する「mvn azure-functions:run」というコマンドは実行していない。


要点まとめ
- Azure App ServiceやAzure Functionsを利用したプログラムは、他のJavaプログラムと同様に、JUnitを利用してテスト用プログラムを書くことができる。
- Azure App Serviceのコントローラクラスのテストは、MockMvcを使ってメソッド呼び出したり、RestTemplateクラスのメソッドをMock化して呼び出したりできる。
- Azure FunctionsでHTTP要求に応じた結果を返却するハンドラークラスのテストを行う際は、生成したAzureSpringBootRequestHandlerクラスのオブジェクトをcloseするのを忘れず行う必要がある。





