앞서 포스팅에서 살펴봤던 것처럼 Repository의 메서드 중에서 멤버십을 DB에 추가하는 테스트 코드를 먼저 작성해보고자 합니다.
MembershipRepository에 대한 테스트 클래스를 작성하면 다음과 같습니다.
public class MembershipRepositoryTests{
@Autowired
private MembershipRepository membershipRepository;
}
Repository 계층은 통합 테스트로 작성할 것이므로, @Repository 계층이 Spring 컨테이너의 빈으로 등록될 것입니다.
실제 프로덕트 코드라면 생성자 주입을 사용해야 하겠지만, 테스트 코드이므로 필드 주입은 @Autowired를 사용해줍니다.
위의 테스트 클래스에서 아직 MembershipRepository는 존재하지 않으므로,빨간 줄을 띄우며 클래스를 찾을 수 없다는 에러가 보입니다.
컴파일 에러를 해결하기 위해 MembershipRepository를 추가해주어야합니다.그리고 또 컴파일 에러를 해결하기 위해 Membership이라는 entity가 추가로 필요합니다.
public interface MembershipRepository extends JpaRepository<Membership, Long> {
}
public class Membership {
}
그러면 이제 컴파일 에러가 해결되었고, 테스트 코드를 실행 할 수 있습니다. 사실 TDD로 개발하면 이러한 클래스들은 테스트 클래스의 Inner 클래스로 만들고 프로덕션 코드로 옮기는게 정상입니다.
테스트 코드를 실행하면 MemberRepository가 Null이므로 테스트에 실패하고 빨간막대를 보게 됩니다.
이를 해결하기 위해 다음과 같은 어노테이션들을 테스트 클래스에 추가하였습니다.
@DataJpaTest //JPA Repository들에 대한 빈들을 등록하여 단위 테스트의 작성을 용이하게 합니다.
@Display("membershipRepository는 Null이 아니다.")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MembershipRepositoryTest{
@Autowired
private MembershipRepository membershipRepository;
@Test
public void membershiptTest(){
Assertions.asserThat(membershipRepository).isNotNull();
}
}
Repository타입의 빈을 등록하기 위해서는 @Repository 어노테이션을 붙여주어야 합니다.
하지만 JpaRepository 하위에 @Repository가 이미 붙어있으므로 생략이 가능합니다.
또한 테스트를 위해서는 테스트 컨텍스트를 잡아주어야 할텐데, @DataJpaTest는 내부적으로 @ExtendWith(SpringExtension.class)어노테이션을 가지고 있어서, 이 어노테이션만 붙여주면 됩니다.
마찬가지로 @DataJpaTest에는 @Transacional어노테이션이 있어서, 테스트의 롤백 등을 위해 별도로 트랜잭션 어노테이션을 추가하지 않아도 됩니다.
그리고 테스트를 실행하면 이번에도 실패하고, 다음과 같은 에러가 발생합니다.
내용을 분석해보면 Membership이 JPA에 의해 관리되는 클래스가 아니라고 하는것이고,Membership을 엔티티로써 관리하기 위해 다음의 코드들을 Membership 클래스에 추가해 주어야합니다.
JPA에서 관리되는 엔티티를 위해서는 기본 생성자가 필요합니다.
그러므로 @NoArgsConstructor
어노테이션도 추가해줍니다.
package org.codeJ.membership.entity;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
@Entity
@NoArgsConstructor
public class Membership {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
그리고 테스트를 실행하면 드디어 첫 초록막대를 볼 수 있습니다.
첫 테스트를 성공하기까지 여러번 테스트를 실행하고, 수정하는 작업을 반복하였습니다. 이러한 작업 흐름이 TDD의 흐름이며 , 이러한 흐름을 유지한 채로, 멤버십을 등록하는 Repository 개발을 이어가보도록 합니다.
MembershipRepository가 Null인지 검사하는 테스트는 이제 불필요하므로 제거합니다.
그리고 다음과 같은 멤버십 등록 테스트를 작성합니다.
테스트의 결과 검증은 응답인 멤버십 ID와 멤버십 이름이 Null이 아님으로 판단합니다.
@Test
@DisplayName("멤버십 등록")
public void registerMembership(){
//given
final Membership membership = Membership.builder()
.userId("userId")
.membershipName("네이버")
.point(10000)
.build();
//when
final Membership result = membershipRepository.save(membership);
//then
assertThat(result.getId()).isNotNull();
assertThat(result.getUserId()).isEqualTo("userId");
assertThat(result.getMembershipName()).isEqualTo("네이버");
assertThat(result.getPoint()).isEqualTo(10000);
}
역시 테스트 코드에서 컴파일 에러들이 발생하고 있고 , 이를 해결하기 위해 다음과 같은 프로덕션 코드들을 추가할 수 있습니다.
package org.codeJ.membership.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Membership {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length=20)
private String membershipName;
@Column(nullable = false)
@ColumnDefault("0")
private Integer point;
@Column(nullable = false)
private String userId;
@CreationTimestamp
@Column(nullable = false,length = 20, updatable = false)
private LocalDateTime createdAt;
@Column(length = 20)
@UpdateTimestamp
private LocalDateTime updatedAt;
}
테스트를 실행하면 다음과 같이 테스트를 성공하고 초록 막대를 보게 됩니다.
테스트가 성공하고 초록 막대를 보았으니 이제는 리팩토링의 단계를 진행해야 합니다. 요구사항으로는 네이버,카카오,라인 3개의 멤버십을 관리할 수 있어야 하는데, 이부분을 Enum으로 관리하면 좋습니다.
그러므로 멤버십 이름을 멤버십 타입으로 관리하도록 합니다.
여기서 다시 TDD기반으로 개발하고 있음을 잊어서는 안됩니다.
테스트 코드에 String으로 되어 있는 membershipName을 Enum 기반의 MembershipType으로 먼저 변경하고, 프로덕트 코드로 변경해줍니다.
@Test
@DisplayName("멤버십 등록")
public void registerMembership(){
//given
final Membership membership= Membership.builder()
.userId("userId")
.membershipName(MembershipType.NAVER)
.point(10000)
.build();
//when
final Membership result = membershipRepository.save(membership);
//then
assertThat(result.getId()).isNotNull();
assertThat(result.getUserId()).isEqualTo("userId");
assertThat(result.getMembershipType()).isEqualTo(MembershipType.NAVER);
assertThat(result.getPoint()).isEqualTo(10000);
}
컴파일 에러를 수정하기 위해 MembershipType이라는 Enum 이라는 클래스를 만들고 Membership 엔티티를 수정해줍니다.
@Getter
@RequiredArgsConstructor
public enum MembershipType {
NAVER("네이버"),
LINE("라인"),
KAKAO("카카오");
private final String companyName;
}
package org.codeJ.membership.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Membership {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Long id;
@Enumerated(EnumType.STRING)
private MembershipType membershipType;
@Column(nullable = false)
@ColumnDefault("0")
private Integer point;
@Column(nullable = false)
private String userId;
@CreationTimestamp
@Column(nullable = false,length = 20, updatable = false)
private LocalDateTime createdAt;
@Column(length = 20)
@UpdateTimestamp
private LocalDateTime updatedAt;
}
하지만 아직 하나 더 추가해야 하는 Repository 테스트가 있습니다. 그것은 바로 어떤 사용자가 이미 멤버십 타입을 등록했으면 중복 검사를 하여 등록되지 않도록 해야 하는 것입니다.
해당 로직을 구현하기 위해서는 사용자의 아이디와 멤버십 테스트로 멤버십을 조회할 수 있어야 합니다.
이에 대한 테스트 코드를 다음과 같이 작성할 수 있습니다.
@Test
@DisplayName("멤버십이 존재하는지에 대한 테스트")
void alreadyMembership(){
//given
final Membership membership = Membership.builder()
.userId("UserId")
.membershipType(MembershipType.NAVER)
.point(10000)
.build();
//when
membershipRepository.save(membership);
membershipRepository.findByUserIdAndMembershipType("userId",MembershipType.NAVER);
//then
assertThat(findResult).isNotNull();
assertThat(findResult.getId()).isNotNull();
assertThat(findResult.getUserId()).isEqualTo("userId");
assertThat(findResult.getMembershipType()).isEqualTo(MembershipType.NAVER);
assertThat(findResult.getPoint()).isEqualTo(10000);
}
package org.codeJ.membership.repository;
import org.codeJ.membership.entity.Membership;
import org.codeJ.membership.entity.MembershipType;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MembershipRepository extends JpaRepository<Membership,Long> {
Membership findByUserIdAndMembershipType(final String userId, final MembershipType membershipType);
}
그리고 테스트를 실행하면 다음과 같이 초록 막대를 볼 수 있습니다.
이렇게 되면 멤버십을 등록하기 위한 API에 필요한 Repositroy의 로직들은 구현이 완료되었습니다.
이제 다음 단계로 Service 계층을 개발해봅니다.