web-dev-qa-db-ja.com

リモートでモックを作成する方法REST Springの単体テストのAPI?

何らかの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を使用する私の代替手段は何ですか?

30
user1019830

Spring RestTemplateを使用する場合は、MockRestServiceServerを使用できます。例はここにあります MockRestServiceServerを使用したRESTクライアントテスト

16
Tuno

最善の方法は、 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)));
11

クライアントをunitテストする場合は、REST APIを作成しているサービスをモックアウトします呼び出し、つまり mockito -これらのAPI呼び出しを行っているサービスがあると思いますか?

一方、他のAPIを「モックアウト」したい場合は、何らかの応答を提供するサーバーがありますが、これはより統合テストのラインになります。多くのフレームワークの1つを試してみてください。 restitorest-driver または betamax

7
theadam

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を使用した並行テストの実行に対して堅牢です。

2
nobar

あなたが探しているのは、Spring MVC Test Frameworkの Client-side REST Tests のサポートです。

NumberClientがSpringのRestTemplateを使用していると仮定すると、前述のサポートがこれを実現する方法です。

お役に立てれば、

サム

1
Sam Brannen

休息を使用し、 DTOパターン を活用している場合、 このチュートリアル に従うことをお勧めします。

0
Manu

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 も使用しています:)