스프링 부트 테스트 실행 시 업로드 된 파일도 롤백 시점 사라질까??
스프링 부트 테스트는 @Transactional 을 테스트 케이스에 선언 시 테스트 완료 후 항상 롤백을 하여, 다음 테스트에 영향을 주지 않는다. (물론 rollback false 지정하는 설정이 있음)
하지만 테스트 실행시 생성된 파일은 데이터 롤백 이후에도 그대로 남아있다.
파일 업로드 후 업로드된 파일을 삭제하는 테스트 코드를 작성해보자
먼저 아래를 보면 파일 업로드 테스트를 위한 파일 정보를 저장하는 엔티티가 있다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "attach_file")
public class AttachFile{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Comment("첨부파일 ID")
@Column(name="file_id", columnDefinition = "bigint")
private long fileId;
@Comment("첨부파일 기존 파일명")
@Column(name="org_file_name", columnDefinition = "varchar(255)")
private String orgFileName;
@Comment("첨부파일 서버 파일명")
@Column(name="dist_file_name", columnDefinition = "varchar(100)")
private String distFileName;
@Comment("첨부파일 확장자")
@Column(name="file_extension", columnDefinition = "varchar(20)")
private String fileExtension;
@Comment("첨부파일 크기")
@Column(name="file_size", columnDefinition = "bigint")
private long size;
@Builder
public AttachFile(long fileId, String orgFileName, String distFileName, String fileExtension, long size) {
this.fileId = fileId;
this.orgFileName = orgFileName;
this.distFileName = distFileName;
this.fileExtension = fileExtension;
this.size = size;
}
}
원래라면 보통 파일 정보를 저장하는 엔티티의 연관관계 주인 테이블이 있지만 테스트를 간단하게 하기 위해 생략..
간단한 파일 정보 저장 서비스도 만들어 본다면
@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {
@Value("${config.upload-file-path}")
private String baseFilePath; // application.yml 에서 base 경로 잡아주기
private final FileRepository fileRepository;
/**
* @param file 업로드할 파일
* @param path 업로드할 경로
* @return
* @throws IOException
*/
@Transactional
public AttachFile uploadFile(MultipartFile file, String path) throws IOException {
// 경로에 디렉토리가 없다면 디렉토리 생성
Files.createDirectories(Paths.get(baseFilePath.concat(path)));
// 실제 파일이름
String originalFileName = file.getOriginalFilename();
// 파일 확장자 찾아오기 .jpg 등
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
// 먼저 파일 업로드 시, 파일명을 난수화하기 위해 random으로 돌린다.
String key = path.concat(UUID.randomUUID().toString().concat(fileExtension));
// 실제 파일 경로에 업로드
FileCopyUtils.copy(file.getBytes(), new File(baseFilePath.concat(key)));
long fileSize = file.getSize();
String originalFilename = file.getOriginalFilename();
AttachFile attachFile = AttachFile.builder()
.orgFileName(originalFilename)
.fileExtension(fileExtension)
.distFileName(String.format("%s%s", baseFilePath, key))
.size(fileSize)
.build();
return fileRepository.save(attachFile);
}
}
파일과 경로를 전달받으면 (프로젝트 기본경로 + 전달받은 경로) 에 파일을 생성하는 간단한 서비스 코드를 만들었다.
(원래는 파일에 대한 유효성검사 올바른 확장자 등 처리 해야 하지만 테스트 편의를위해 생략)
참고로 baseFilePath 는 application.yml 에 설정한 디렉터리 베이스 위치
준비작업이 모두 끝났으니 이제 파일 서비스를 테스트 하는 코드만 작성만 남았다.
먼저 MockMultipartFile을 사용해 테스트를 할건데 간단히 설명하자면
순서대로 파라미터 이름, 파일 이름, 타입, 실제 전송할 파일이 존재하는 위치를 넣어주면 된다.
@Slf4j
@Transactional
@SpringBootTest
class FileServiceTest {
@Autowired
private FileService fileService;
@DisplayName("단건 파일업로드 테스트")
@Test
void 파일업로드테스트() throws IOException {
//given
MockMultipartFile image = new MockMultipartFile(
"test", // 파일의 파라미터 이름
"spring.png", // 실제 파일 이름
"image/png", // 파일의 확장자 타입
new FileInputStream(new File("C:/spring.png")) // 실제 파일
);
// when
AttachFile attachFile = fileService.uploadFile(image, "profile/");
// then
Assertions.assertThat(attachFile).isNotNull();
}
}
파일이 주어졌을때 위에서 만든 파일 서비스에 image 파일과 profile/ 이라는 경로를 전달한다.
그렇다면 프로젝트 베이스 경로/profile/파일.png 로 생성된다면 파일 업로드가 잘 된 것으로 볼 수 있다.
먼저 테스트를 실행해보면
실제 경로에 의도한 대로 잘 생성된 것을 확인할 수 있다.
이제 파일을 삭제하는 코드를 추가해보자
@Slf4j
@Transactional
@SpringBootTest
class FileServiceTest {
@Autowired
private FileService fileService;
@DisplayName("단건 파일업로드 테스트")
@Test
void 파일업로드테스트() throws IOException {
//given
MockMultipartFile image = new MockMultipartFile(
"test.jpg",
"spring.png",
"image/png",
new FileInputStream(new File("C:/spring.png")));
// when
AttachFile attachFile = fileService.uploadFile(image, "profile/");
// then
String filePath = attachFile.getDistFileName();
String dirPath = filePath.substring(0,filePath.lastIndexOf("/"));
//파일 삭제 추가
boolean deleteFile = new File(filePath).delete();
boolean deleteDir = new File(dirPath).delete();
log.info("filePath = {}", filePath);
log.info("dirPath = {}", dirPath);
log.info("deleteFile = {}", deleteFile);
log.info("deleteDir = {}", deleteDir);
Assertions.assertThat(attachFile).isNotNull();
Assertions.assertThat(deleteFile).isTrue();
}
}
java.io.File 의 delete() 메서드를 사용해 간단하게 파일을 삭제 할 수 있다.
dirPath를 구해 파일 삭제 이후 디렉터리 삭제 까지 가능한데 delete() 메서드는 친절하게도 이미 존재하는 파일이 있다면
폴더를 삭제하지 않고 false를 반환 해 준다.(삭제성공시 true 반환)
그렇다면 예상 결과는 아까 업로드 한 파일이 남아있으니 deleteDir은 false, deleteFile은 true가 나온다면 성공