[Spring]단위 테스트 Mockito willReturn(Optional<T>) 오류 해결
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));
}
❓ 근데 여기서 의문이 하나 생겼다
@BeforeEach로 setUp 메서드에서 테스트 초반 세팅을 하는데, 왜 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의 given과 willReturn은 Optional<T> 같은 타입도 잘 다룬다. 타입 문제라고 생각하기 전에 변수 가시성부터 체크하자.
- @BeforeEach의 setUp은 테스트마다 초기화해주는 역할일 뿐, 그 안에서 만든 로컬 변수는 메서드 스코프를 벗어나면 못 쓴다. 클래스 필드로 만들어야 공유할 수 있다는 걸 명확히 깨달았다.