Spring Boot-1 Spring Boot RepositoryとService Layerテスト


現在、多くの会社がREST方式のAPIを使用する傾向にある.
これは,我々がマイクロサービスアーキテクチャを採用し,Front SideとBackEnd Sideを完全に分離したためである.
まずサービス・レイヤを作成し、テストを行います.

1.依存性の追加

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'com.h2database:h2'
'org.springframework.boot:spring-boot-start-web":Web関連モジュール
'org.springframework.boot:spring-boot-start-data-jpa":jpa関連モジュール
'com.h 2 database:h 2':組み込みデータベースを使用し、テスト環境でも使用
依存項目を追加して実験を行います.

2.h 2接続を確認する


データベースに簡単なデータを格納し、再度クエリーを行い、入力値と一致する値があるかどうかをテストします.

2.1ユーザーエンティティの作成

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String email;
    private String password;
}

2.1.1 @Entity

spring-boot-starter-data-jpa依存項目を追加し、@Entityを追加すると、テーブルとJavaクラスがマッピングされます.

2.1.2 @Id


テーブルの識別子を設定します.メインキーになるでしょう

2.1.3 @GeneratedValue


ポリシーはGenerationTypeです.AUTOで作るデータベースの読み込み時に自動的に管理されます.

2.1.4 @AllArgsConstructor , @NoArgsConstructor


すべてのパラメータのタイプの作成者を作成します.
パラメータのないジェネレータを作成します.

2.2ユーザーレポートの作成

import com.plee.auth.domain.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
Springが提供する抽象インタフェースは、データベースの一貫した方法でアクセスできます.

2.3 RepositoryTestの作成

import com.plee.auth.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.Optional;

@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles(value = "dev")
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void saveUserTest() {
        User user = new User(null,"name", "password");
        User savedUser = userRepository.save(user);
        Assertions.assertEquals(user.getEmail(), savedUser.getEmail(),
                "saveUserTest");
        Assertions.assertEquals(user.getPassword(), savedUser.getPassword(),
                "saveUserTest");
    }

    @Test
    public void findByEmailSuccessTest() {
        User user = new User(null, "name1", "password");
        User savedUser = userRepository.save(user);

        Optional<User> userFindByEmail = userRepository.findByEmail(user.getEmail());
        userFindByEmail.ifPresent(value -> Assertions.assertEquals(savedUser.getEmail(), value.getEmail()));
    }

    @Test
    public void findByEmailFailureTest() {

        Optional<User> userFindByEmail = userRepository.findByEmail("not exist email");
        Assertions.assertEquals(Optional.empty(), userFindByEmail);
    }

    @Test
    public void idStrategyTest() {
        User user1 = new User(null, "name1", "password");
        User user2 = new User(null,"name2", "password");
        User savedUser1 = userRepository.save(user1);
        User savedUser2 = userRepository.save(user2);

        Assertions.assertEquals(1, Math.abs(savedUser1.getId() - savedUser2.getId()));

    }

}
ユーザーの作成と保存をテストしてみましょう.

2.3.1 @DataJpaTest


初めて登場したマネです
次に、JPAに関するテスト設定のみをロードできます.
DataSourceの設定やJPAに関するテストを実行できます.

2.3.2 saveUserTest


新しいユーザーを作成し、保存したユーザーと同じであることを確認します.

2.3.3 findByNameTest


ユーザー・レポートの作成時にメソッドを追加し、保存した名前でメソッドが見つかったかどうかをテストします.

2.3.4 idStrategyTest


Databaseにidの生成を管理させます.従って、連続して格納される2つのデータのid値は1差である.
もちろん、本番環境で適切なIsolationが指定されていない場合は、テストが正常に動作しない可能性があります.
user 1を保存するときに別のuserを保存する人がいる場合、差は2になる可能性があります.

3. UserService


まずサービスレイヤーを作成します.

3.1 UserService Interface

import com.plee.auth.domain.User;

public interface UserService {
    User add(User user);
    User get(Long id);
    User update(User user);
    boolean delete(Long id);
    User findByEmail(String email);
}
その後、コントローラは、複数のユーザサービスを使用するためのインタフェースを作成します.実際,これらのサービスはあまり変化していないが,インタフェースを作成する習慣を身につけることで依存性を低減することはメンテナンスにおいて非常に重要な役割を果たす.

3.2 UserServiceImpl

import com.plee.auth.domain.User;
import com.plee.auth.exception.UserExistedException;
import com.plee.auth.exception.UserNotFoundException;
import com.plee.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{

    final boolean DELETE_SUCCESS = true;
    final boolean DELETE_FAILED = false;

    private UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public User add(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent())
            throw new UserExistedException("User is already joined : "+user.getEmail());
        else
            return userRepository.save(user);
    }

    @Override
    public User get(Long id) {
        return userRepository.findById(id).orElseThrow(
                () -> new UserNotFoundException("delete: User not found by : " + id));
    }

    @Override
    public User update(User user) {
        if (userRepository.findById(user.getId()).isPresent())
            return userRepository.save(user);
        else
            throw new UserNotFoundException("update: User not found by : " + user.getId());
    }

    @Override
    public boolean delete(Long id) {
        userRepository.deleteById(id);
        if (!userRepository.findById(id).isPresent())
            return DELETE_SUCCESS;
        else
            return DELETE_FAILED;
    }

    @Override
    public User findByEmail(String email) {
        return userRepository.findByEmail(email).orElseThrow(
                () -> new UserNotFoundException("findbyEmail: User not found by : " + email));
    }
}
ユーザーレポートを入力して、各メソッドを実装します.

3.2.1 CustomException

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.CONFLICT)
public class UserExistedException extends RuntimeException{
    public UserExistedException(String message) {
        super(message);
    }
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException(String message) {
        super(message);
    }
}
サービス層に使用するExceptionを作成します.
@ResponseStatus定義後、コントローラテストにエラーが発生した場合、どのHttpStatusが返されますか.

3.3 UserServiceImplTest


通常、各レイヤをテストするときは、クラスが持つ役割を考慮する必要があります.
通常、サービス・レベルには次の役割があります.
  • ビジネスロジック(このセクションでは、ドメインがビジネスロジックの責任者であると考えている本が多く、トビーのspringでは、ビジネスロジックはサービス層が担当することが望ましい.アプリケーションは十分に処理できるが、データベースをデータベースに渡すとデータベースの負荷が増加する.)
  • 事務管理主体
  • まだいくつかあるかもしれませんが、上の2つだと思います.
    まず、ビジネスロジックについてユニットテストを行います.

    3.3.1ユーザーServiceImplデバイステストの依存性の変更


    UserServiceImplは、UserRepositoryに依存します.
    しかし、ユニットテストは対応する方法に集中しなければならない.他の依存性を直接注入してテストすることを避ける.
    したがって、これらの依存性がある場合は、これらのライブラリを使用できます.

    3.3.1.1 Mockito


    テスト時にターゲットオブジェクトを使用してテストを支援するライブラリ.
    スプリングのデフォルトで提供されるライブラリバージョンにはホットスポットが含まれています.これは依存性を変える.
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.1'
    既存の5.2版では、いくつかの問題があります.

    3.3.2 UserServiceImplTest

    import com.plee.auth.domain.User;
    import com.plee.auth.exception.UserNotFoundException;
    import com.plee.auth.repository.UserRepository;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
    
    import java.util.Optional;
    
    import static org.mockito.ArgumentMatchers.*;
    import static org.mockito.BDDMockito.given;
    import static org.mockito.Mockito.*;
    
    
    @ExtendWith(MockitoExtension.class)
    public class UserServiceImplTest {
    
        @Mock
        private UserRepository userRepository;
    
        @InjectMocks
        private UserService userService = new UserServiceImpl(this.userRepository);
    
        @Test
        void addUserSuccessfully() {
            final User user = new User(null, "name", "password");
            given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.empty());
            given(userRepository.save(user)).willReturn(user);
    
            User savedUser = userService.add(user);
            Assertions.assertNotNull(savedUser);
            verify(userRepository).findByEmail(anyString());
            verify(userRepository).save(any(User.class));
        }
    
        @Test
        void addUserFailure() {
            final User user = new User(1L, "name", "password");
            given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.of(user));
            Assertions.assertThrows(UserNotFoundException.class, () -> userService.add(user));
            verify(userRepository, never()).save(any(User.class));
        }
    
        @Test
        void getUserSuccessfully() {
            final User user = new User(1L, "name", "password");
            given(userRepository.findById(user.getId())).willReturn(Optional.of(user));
            User userByGet = userService.get(user.getId());
            Assertions.assertEquals(user, userByGet);
            verify(userRepository).findById(anyLong());
        }
    
        @Test
        void getUserFailure() {
            given(userRepository.findById(anyLong())).willReturn(Optional.empty());
            Assertions.assertThrows(UserNotFoundException.class, () -> userService.get(anyLong()));
            verify(userRepository).findById(anyLong());
        }
    
        @Test
        void updateSuccessfully() {
            final User user = new User(1L, "name", "password");
            given(userRepository.findById(user.getId())).willReturn(Optional.of(user));
            given(userRepository.save(user)).willReturn(user);
            User updatedUser = userService.update(user);
            Assertions.assertNotNull(updatedUser);
            verify(userRepository).save(any(User.class));
        }
    
        @Test
        void updateFailure() {
            final User user = new User(1L, "name", "password");
            given(userRepository.findById(anyLong())).willReturn(Optional.empty());
            Assertions.assertThrows(UserNotFoundException.class, () -> userService.update(user));
            verify(userRepository).findById(anyLong());
        }
    
        @Test
        void deleteSuccessfully() {
            final Long id = 1L;
            userService.delete(id);
            verify(userRepository, times(1)).deleteById(anyLong());
            verify(userRepository, times(1)).findById(anyLong());
    
        }
    
        @Test
        void findByEmailSuccessfully() {
            final User user = new User(1L, "name", "password");
            given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.of(user));
            User userByEmail = userService.findByEmail(user.getEmail());
            Assertions.assertEquals(user, userByEmail);
            verify(userRepository).findByEmail(any(String.class));
        }
    
        @Test
        void findByEmailFailure() {
            final String email = "email";
            given(userRepository.findByEmail(email)).willReturn(Optional.empty());
            Assertions.assertThrows(UserNotFoundException.class, () -> userService.findByEmail(email));
            verify(userRepository).findByEmail(any(String.class));
        }
    }

    3.3.2.1 Mockオブジェクトを使用したテスト


    コードの理解を助けるために、いくつかの説明を追加します.

    @Mock


    ターゲットオブジェクトを作成します.

    @InjectMock


    依存性を注入するターゲットオブジェクトを作成します.

    3.2.2.2コードで使用する方法を見てみましょう。

    @Test
    void addUserSuccessfully() {
        final User user = new User(null, "name", "password");
        given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.empty());
        given(userRepository.save(user)).willReturn(user);
        User savedUser = userService.add(user);
        Assertions.assertNotNull(savedUser);
        verify(userRepository).findByEmail(anyString());
        verify(userRepository).save(any(User.class));
    }

    given


    メソッドの条件.
    given(userRepository.findByEmail(user.getEmail())) : userRepository.findByEmail(user.getEmail()を呼び出すと
    willReturn(Optional.empty()) : Optional.空に戻る()
    verify(userRepository).findByEmail(anyString()); : userRepositoryがfindByEmailを呼び出すかどうかを確認します

    3.3.3ユニットテスト


    既存のユーザー・レポートを考慮しない場合は、サービス・レイヤのテストのみが作成されます.
    ユーザーが正常に稼働していることを報告し、サービス・レベルのテストを実施すると仮定します.
    このように孤立したユニットテストを行ってこそ、完全なビジネスロジックテストに集中することができます.
    次のヒントを続けましょう