3

I want to unit test the following class:

@Data
@AllArgsConstructor
public class MyClass {

   private MyValueClass valueObject;

   public BigDecimal someMethod(int startId, int endId) {

      List<BigDecimal> values = valueObject.get(startId, endId);
      
      ...

I tried to mock MyValueClass which looks like:

@Value //lombok annotation
public class MyValueClass {

   private List<Data> someData;

   public List<BigDecimal> get(int startId, int endId) {

       //code to get subset of someData with ids between startId and endId

   }

But when I run this junit (jupiter) test:

@ExtendWith(MockitoExtension.class)
class MyClassTest {

   private MyClass myClass;

   @Mock
   private MyValueClass valueOjectMock;

   @BeforeEach
   public void setUp() {
      myClass= new myClass(valueOjectMock);
   }

   @Test
   void test() {
      when(valueOjectMock.get(1,5))
         .thenReturn(new ArrayList<>());
      ....
   }
}

I get the following error:

org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class com.seasset.funds.performance.domain.FundReturns
Mockito cannot mock/spy because :
 - final class
    at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:153)

But MyValueClass is not final. Why do I get this error? How can I mock this class?

EricSchaefer
  • 25,272
  • 21
  • 67
  • 103
James
  • 2,876
  • 18
  • 72
  • 116
  • 2
    Why do you want to mock this value object? When writing unit tests you should avoid mocking value objects as this makes your tests brittle, hard to maintain, and doesn't favor refactorings. It also _violates_ one of the [Golden Mockito Rules](https://github.com/mockito/mockito/wiki#remember) – rieckpil Jun 17 '21 at 10:32

2 Answers2

3

The Lombok annotation @Value on your MyValueClass makes your class final by default: lombok value feature. If you are using Mockito version 1, you cannot mock final classes: reference answer for mocking final classes. Try using PowerMockito or Mockito v2 mockito-inline: if you are using maven:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>3.9.0</version>
    <scope>test</scope>
</dependency>

If you want to continue testing with mockito v1, try replacing @Value with @Data if it doesn't affect the performance.

coloma1984
  • 101
  • 1
  • 4
3

A value object should never be mocked. It is just a immutable value after all (that's the reason why lombok makes it final). If it contains complicated logic or hidden mutable state (and therefore is not a real value object), you should not use @Value but use the individual pieces that you need (like @Getter).

EricSchaefer
  • 25,272
  • 21
  • 67
  • 103
  • Thanks. +1 What is considered complicated logic? I only have the one `get` method that has some logic to return a subset of `someData`. Specifically, it returns only `Data` objects from `someData` that have an id between `startId` and `endId`. It returns an empty list though if `someData` doesn't include the `startId` and `endId`. – James Jun 17 '21 at 21:44
  • 1
    This I would not consider "complicated logic". I have seen people define a "value object" (i.e. all members would be immutable), that would call external web services. – EricSchaefer Jun 18 '21 at 10:41