web-dev-qa-db-ja.com

生成されたマッパーを使用およびテストする適切な方法

最近、システムの開発中に競合が発生しました。チームでのテストには3つの異なるアプローチがあることを発見しました。どのアプローチが最適であるかを判断し、これ以上の方法がないかどうかを確認する必要があります。

まず、いくつかの事実に直面しましょう:
-システムには3つのデータ層があります(DTO、ドメインオブジェクト、テーブル)
-mapstructで生成されたマッパーを使用して、各レイヤーのオブジェクトを別のレイヤーにマップしています
-私たちはmockitoを使用しています
-各レイヤーのユニットテストを行っています

ここで競合:ExampleServiceを使用してExampleModelMapperExampleModelにマップし、テストが必要ないくつかの追加のビジネスロジックを実行しているExampleModelDtoをテストするとします。 。返されたデータが正しいことを3つの異なる方法で確認できます。

a)返されたオブジェクトの各フィールドを手動で期待される結果と比較できます。

assertThat(returnedDto)
                .isNotNull()
                .hasFieldOrPropertyWithValue("id", expectedEntity.getId())
                .hasFieldOrPropertyWithValue("address", expectedEntity.getAddress())
                .hasFieldOrPropertyWithValue("orderId", expectedEntity.getOrderId())
                .hasFieldOrPropertyWithValue("creationTimestamp", expectedEntity.getCreationTimestamp())
                .hasFieldOrPropertyWithValue("price", expectedEntity.getPrice())
                .hasFieldOrPropertyWithValue("successCallbackUrl", expectedEntity.getSuccessCallbackUrl())
                .hasFieldOrPropertyWithValue("failureCallbackUrl", expectedEntity.getFailureCallbackUrl())

b)実際のマッパー(通常のロジックと同じ)を使用して、2つのオブジェクトを比較できます。

assertThat(returnedDto).isEqualToComparingFieldByFieldRecursivly(mapper.mapToDto(expectedEntity)))

c)最後に、マッパーとその応答をモックできます。

final Entity entity = randomEntity();
final Dto dto = new Dto(entity.getId(), entity.getName(), entity.getOtherField());
when(mapper.mapToDto(entity)).thenReturn(dto);

弾力性があり、変更に耐えられるようにしながら、テストをできる限り改善したいと考えています。また、DRY原則を守りたいと考えています。

各メソッドのアドバイス、コメント、長所、短所を聞いて喜んでいます。また、他の解決策も公開しています。

こんにちは。

4
ŁukaszG

ここで私がアドバイスする2つのオプションがあります。

オプション1(サービスとマッパー用の個別の単体テストスイート)

単体テストを行う場合は、サービス内のマッパー(OFCと同様に他の依存関係)をモックして、サービスロジックのみをテストします。マッパー用に、個別の単体テストスイートを作成します。ここにコード例を作成しました: https://github.com/jannis-baratheon/stackoverflow--mapstruct-mapper-testing-example

例からの抜粋:

サービスクラス:

public class AService {
    private final ARepository repository;
    private final EntityMapper mapper;

    public AService(ARepository repository, EntityMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    public ADto getResource(int id) {
        AnEntity entity = repository.getEntity(id);
        return mapper.toDto(entity);
    }
}

マッパー:

import org.mapstruct.Mapper;

@Mapper
public interface EntityMapper {
    ADto toDto(AnEntity entity);
}

サービス単体テスト:

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;

public class AServiceTest {

    private EntityMapper mapperMock;

    private ARepository repositoryMock;

    private AService sut;

    @Before
    public void setup() {
        repositoryMock = mock(ARepository.class);
        mapperMock = mock(EntityMapper.class);

        sut = new AService(repositoryMock, mapperMock);
    }

    @Test
    public void shouldReturnResource() {
        // given
        AnEntity mockEntity = mock(AnEntity.class);
        ADto mockDto = mock(ADto.class);

        when(repositoryMock.getEntity(42))
                .thenReturn(mockEntity);
        when(mapperMock.toDto(mockEntity))
                .thenReturn(mockDto);

        // when
        ADto resource = sut.getResource(42);

        // then
        assertThat(resource)
                .isSameAs(mockDto);
    }
}

マッパー単体テスト:

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.Before;
import org.junit.Test;

public class EntityMapperTest {

    private EntityMapperImpl sut;

    @Before
    public void setup() {
        sut = new EntityMapperImpl();
    }

    @Test
    public void shouldMapEntityToDto() {
        // given
        AnEntity entity = new AnEntity();
        entity.setId(42);

        // when
        ADto aDto = sut.toDto(entity);

        // then
        assertThat(aDto)
            .hasFieldOrPropertyWithValue("id", 42);
    }
}

オプション2(サービスとマッパーの統合テスト+マッパーユニットテスト)

2番目のオプションは、実際のマッパーをサービスに注入する統合テストを行うことです。ただし、統合テストでのマッピングロジックの検証には、あまり力を入れないように強くお勧めします。混乱する可能性が非常に高いです。マッピングをスモークテストし、マッパーの単体テストを個別に記述します。

概要

総括する:

  • サービスの単体テスト(モックマッパーを使用)+マッパーの単体テスト
  • サービスの統合テスト(実際のマッパーを使用)+マッパーの単体テスト

私は通常、2番目のオプションを選択します。ここでは、MockMvcを使用してメインアプリケーションパスをテストし、小さいユニットの完全なユニットテストを記述します。

2
jannis

ExampleServiceをテストするには、マッパーテストとMapperImplテストから動作を分離して、マッパーとその応答をモック化することをお勧めします。

しかし、Mapperインスタンスを単体テストする必要があります。これは、モックデータでテストするか、フィクスチャを使用してテストすることもできます。

Mapperで導入されたビジネスロジック(マッピングルール)をテストするために、MapperImplクラスに対してテストできます。

1
Pradip Karki