最近、システムの開発中に競合が発生しました。チームでのテストには3つの異なるアプローチがあることを発見しました。どのアプローチが最適であるかを判断し、これ以上の方法がないかどうかを確認する必要があります。
まず、いくつかの事実に直面しましょう:
-システムには3つのデータ層があります(DTO、ドメインオブジェクト、テーブル)
-mapstructで生成されたマッパーを使用して、各レイヤーのオブジェクトを別のレイヤーにマップしています
-私たちはmockitoを使用しています
-各レイヤーのユニットテストを行っています
ここで競合:ExampleService
を使用してExampleModelMapper
をExampleModel
にマップし、テストが必要ないくつかの追加のビジネスロジックを実行している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原則を守りたいと考えています。
各メソッドのアドバイス、コメント、長所、短所を聞いて喜んでいます。また、他の解決策も公開しています。
こんにちは。
ここで私がアドバイスする2つのオプションがあります。
単体テストを行う場合は、サービス内のマッパー(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番目のオプションを選択します。ここでは、MockMvc
を使用してメインアプリケーションパスをテストし、小さいユニットの完全なユニットテストを記述します。
ExampleService
をテストするには、マッパーテストとMapperImpl
テストから動作を分離して、マッパーとその応答をモック化することをお勧めします。
しかし、Mapper
インスタンスを単体テストする必要があります。これは、モックデータでテストするか、フィクスチャを使用してテストすることもできます。
Mapperで導入されたビジネスロジック(マッピングルール)をテストするために、MapperImpl
クラスに対してテストできます。