何らかのURIでRESTful APIを公開しているリモートWebサービスを使用する単純なクライアントをアプリケーションで作成したとします/foo/bar/{baz}
。次に、このWebサービスを呼び出すクライアントをユニットテストします。
理想的には、私のテストでは、/foo/bar/123
または /foo/bar/42
。私のクライアントは、APIが実際にどこかで実行されていると想定しているため、http://localhost:9090/foo/bar
私のテスト。
Spring MVC TestフレームワークでSpringコントローラーをテストするのと同様に、単体テストを自己完結型にしたいのです。
リモートAPIから数値を取得する単純なクライアント用の擬似コード:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
@Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
@Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
Springを使用する私の代替手段は何ですか?
Spring RestTemplate
を使用する場合は、MockRestServiceServer
を使用できます。例はここにあります MockRestServiceServer
を使用したRESTクライアントテスト 。
最善の方法は、 WireMock を使用することです。次の依存関係を追加します。
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
以下に示すように、ワイヤーモックを定義して使用します
@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));
クライアントをunitテストする場合は、REST APIを作成しているサービスをモックアウトします呼び出し、つまり mockito -これらのAPI呼び出しを行っているサービスがあると思いますか?
一方、他のAPIを「モックアウト」したい場合は、何らかの応答を提供するサーバーがありますが、これはより統合テストのラインになります。多くのフレームワークの1つを試してみてください。 restito 、 rest-driver または betamax 。
Mockitoを使用して、REST API inSpring Boot。
テストツリーにスタブコントローラーを配置します。
@RestController
public class OtherApiHooks {
@PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(@PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
クライアントは、テストの実行時にlocalhostでAPIを呼び出す必要があります。これはsrc/test/resources/application.properties
で設定できます。テストがRANDOM_PORT
を使用している場合、テスト対象のクライアントはその値を見つける必要があります。これは少し難しいですが、問題はここで解決されます: Spring Boot-実行中のポートを取得する方法
WebEnvironment
(実行中のサーバー)を使用するようにテストクラスを設定すると、テストは標準の方法でMockitoを使用できるようになり、必要に応じてResponseEntity
オブジェクトを返します。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
@MockBean private OtherApiHooks otherApiHooks;
@Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
また、これを使用して、上記で作成したモックを使用した環境でマイクロサービス全体のエンドツーエンドのテストを行うこともできます。これを行う1つの方法は、テストクラスにTestRestTemplate
を挿入し、それを使用してyourREST APIの代わりにAPI clientFunctionUnderTest
の例から。
@Autowired private TestRestTemplate restTemplate;
@LocalServerPort private int localPort; // you're gonna need this too
OtherApiHooks
はテストツリーの@RestController
であるため、Spring BootはSpringBootTest.WebEnvironment
の実行時に指定されたRESTサービスを自動的に確立します。
ここではMockitoを使用して、コントローラークラスをモックします。サービス全体ではありません。したがって、モックがヒットする前に、Spring Bootによって管理されるサーバー側の処理がいくつかあります。これには、例に示されているパスUUIDの逆シリアル化(および検証)などが含まれます。
私の知る限り、このアプローチはIntelliJとMavenを使用した並行テストの実行に対して堅牢です。
あなたが探しているのは、Spring MVC Test Frameworkの Client-side REST Tests のサポートです。
NumberClient
がSpringのRestTemplate
を使用していると仮定すると、前述のサポートがこれを実現する方法です。
お役に立てれば、
サム
Mockito でControllerクラスをモックする基本的な例を次に示します。
Controllerクラス:
_@RestController
@RequestMapping("/users")
public class UsersController {
@Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
_
Beanを構成します。
_@Configuration
public class UserConfig {
@Bean
public UsersController usersController() {
return new UsersController();
}
@Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
_
UserCollectionItemDto
は単純な [〜#〜] pojo [〜#〜] であり、APIコンシューマーがサーバーに送信するものを表します。 UserProfileは、サービスレイヤーで(UserService
クラスによって)使用されるメインオブジェクトです。この動作は DTOパターン も実装します。
最後に、予想される動作をモックアップします。
_@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@Import(UserConfig.class)
public class UsersControllerTest {
@Autowired
private UsersController usersController;
@Autowired
private UserService userService;
@Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
_
アイデアは、純粋なController Beanを使用して、そのメンバーをモックアップすることです。この例では、ユーザーを含むようにUserService.getUsers()
オブジェクトをモックし、コントローラーが適切な数のユーザーを返すかどうかを検証しました。
同じロジックを使用して、サービスおよびアプリケーションの他のレベルをテストできます。この例では Controller-Service-Repository Pattern も使用しています:)