프로젝트/트러블슈팅

[Spring]단위 테스트 Mockito willReturn(Optional<T>) 오류 해결

코딩로봇 2025. 5. 19. 23:12

intro

  • 프로젝트를 진행하며 발생한 문제 상황과 해결 과정들을 상세히 기록하고 추후에 같은 문제가 발생 했을때 빠르게 문제 해결하기 위해 트러블 슈팅을 정리할려고 한다.
  • 기록하는 습관을 기르기 위해 프로젝트 기간동안 꾸준히 작성할 것 이다.

⚠️ 1. 문제 상황 발생

Optional.of(product) Cannot resolve method 'willReturn(Optional<T>)' 오류 때문에 테스트가 안 됐다.

 

 

Mockito의 given 메서드에서 Optional.of(product)willReturn으로 설정하려 했는데, product 변수가 안 잡혀서 컴파일 오류가 난 거였다.

@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

    @Mock
    private ProductRepositoryImpl productRepositoryImpl;

    @InjectMocks
    private ProductService productService;

    @BeforeEach
    void setUp() {
        CreateUserRequestDto dto = new CreateUserRequestDto("test@example.com", "Test User", "testnick", "test123123", "010-1234-5678", "ADMIN");
        User user = new User(dto, "test123123");
        Product product = new Product(1L, ProductCategory.BOOKS, "도라에몽", "내용", new BigDecimal(7000), (long) 1000, user, Status.EXIST.isValue());
    }

    @Test
    void 재품_상세조회() {
        given(productRepositoryImpl.getTodoByIdWithUser(1L)).willReturn(Optional.of(product));
    }
}

 

 

🔎 2. 원인 추론

1. import 문제 

 

given 메소드는 Mock 뿐만아니라 다양하게 존재하기 때문에 잘 선택했는지 확인하였다.

import static org.mockito.BDDMockito.given;

 

코드에는 해당 import 를 잘 해두었다.

 

 

2. 반환 데이터 타입 문제

 

Optional 반환 타입이 아니라서 오류가 날 수 도있기 때문에 찾아봤는데 잘 정의 되어있다.

 

⭕ 3. product 변수가 로컬 변수로 선언

@BeforeEach 메서드 내에서 Product product 를 선언했기 때문에, 이 변수는 setUp 메서드 외부에서 접근할 수 없었다.

@BeforeEach
    void setUp() {
        CreateUserRequestDto dto = new CreateUserRequestDto("test@example.com", "Test User", "testnick", "test123123", "010-1234-5678", "ADMIN");
        User user = new User(dto, "test123123");
        Product product = new Product(1L, ProductCategory.BOOKS, "도라에몽", "내용", new BigDecimal(7000), (long) 1000, user, Status.EXIST.isValue());
    }

    @Test
    void getProduct() {
        given(productRepositoryImpl.getTodoByIdWithUser(1L)).willReturn(Optional.of(product));
    }

 

❓ 근데 여기서 의문이 하나 생겼다

@BeforeEachsetUp 메서드에서 테스트 초반 세팅을 하는데, 왜 setUp 안에서 만든 product 변수를 테스트 메소드에서 못쓰는 거지?

 

product이 로컬 변수였다

@BeforeEach 메서드 안에서 Product product를 선언했기 때문에, 이 변수는 setUp 메서드 안에서만 살았다. 메서드가 끝나면 로컬 변수는 사라지니까 getProduct 테스트 메서드에서 product를 부르려 하니 컴파일러가 "그런 변수 없어!"라고 한 거였다.

 

 

setUp 안의 변수를 못 쓰나?

@BeforeEach는 각 테스트 메서드 실행 전에 호출돼서 초기화 작업을 해준다.

하지만 setUp 안에서 선언된 변수는 그 메서드의 스코프에 갇혀 있어서 클래스 전체에서 공유되지 않는다

. 테스트 메서드에서 setUp의 변수를 쓰려면, 그 변수를 클래스 필드로 만들어서 setUp에서 초기화하고 다른 메서드에서도 접근할 수 있게 해야 한다.

이게 Java의 기본 스코프 규칙인데, 처음엔 이걸 간과했다.

 

📝  3.해결방안

Product product를 클래스 필드로 선언하고, @BeforeEach 메서드에서 초기화하게 했다.

이렇게 하니까 getProduct 테스트 메서드에서 product를 문제없이 쓸 수 있었다.

 

 Product product;

    @BeforeEach
    void setUp() {
        CreateUserRequestDto dto = new CreateUserRequestDto("test@example.com", "Test User", "testnick", "test123123", "010-1234-5678", "ADMIN");
        User user = new User(dto, "test123123");
        product = new Product(1L, ProductCategory.BOOKS, "도라에몽", "내용", new BigDecimal(7000), (long) 1000, user, Status.EXIST.isValue());
    }

    @Test
    void getProduct() {
        given(productRepositoryImpl.getTodoByIdWithUser(1L)).willReturn(Optional.of(product));
    }

 

 

📌4.결과 확인

빨간 줄이 사라졌다.

✒️회고

- 단위 테스트 짤 때 @BeforeEach에서 초기화한 객체를 테스트 메서드에서 쓰려면 클래스 필드로 선언해야 한다.

로컬 변수는 메서드 밖에서 못 쓴다는 기본적인 걸 놓쳤다.

 

- Mockito의 givenwillReturnOptional<T> 같은 타입도 잘 다룬다. 타입 문제라고 생각하기 전에 변수 가시성부터 체크하자.

 

- @BeforeEachsetUp은 테스트마다 초기화해주는 역할일 뿐, 그 안에서 만든 로컬 변수는 메서드 스코프를 벗어나면 못 쓴다. 클래스 필드로 만들어야 공유할 수 있다는 걸 명확히 깨달았다.