12

What is the difference between @SpyBean from org.springframework.boot.test.mock.mockito.SpyBean and @Spy from org.mockito.Spy?

Using @SpyBean instead of @Spy makes my tests fail.

LiTTle
  • 1,811
  • 1
  • 20
  • 37

2 Answers2

18

@Spy doc says:

A field annotated with @Spy can be initialized explicitly at declaration point. Alternatively, if you don't provide the instance Mockito will try to find zero argument constructor (even private) and create an instance for you.

@SpyBean doc says:

Annotation that can be used to apply Mockito spies to a Spring ApplicationContext.

All beans in the context of the same type will be wrapped with the spy. If no existing bean is defined a new one will be added.

So the main difference is @SpyBean is a Spring Boot specific annotation but @Spy is part of Mockito itself. @SpyBean and @Spy basically do the same, but @SpyBean can resolve the Spring specific dependencies, e.g. @Autowired, @Spy can only create object with empty constructor.

csenga
  • 3,819
  • 1
  • 21
  • 31
  • So, `@SpyBean` will load the bean from the instance which already exists in Spring container and `@Spy` will create a totally new object no matter what if already exists in the Spring container or not? – LiTTle Feb 18 '19 at 11:02
  • 1
    `@Spy` works as you said, but in case of `@SpyBean` the doc says: "All beans in the context of the same type will be wrapped with the spy. If no existing bean is defined a new one will be added. " – csenga Feb 18 '19 at 11:44
1

Something I noticed during testing is that SpyBean can result in certain values being carried over between tests whereas Spy always starts from a clean slate. In my case I had @SpyBean set on a class that initially had autowired components. But after I had refactored the class to remove them I did not change the test classes and I had an unexpected failure.

Class1 {
boolean boo;

myMethodA() {
    if (something) {
        boo=true;
    }
}

myMethodB() {
    if (boo) {
      doThis();
    } else { 
      doThat();
    }
  }
}

Both Test1 and Test2 used the @SpyBean Class1. Both Test1 and Test2 ran successfully in isolation but when run as a suite Test2 would fail if it ran after Test1.

Test1 set a boolean value in Class1 to true, which was still true when Test2 ran. This caused Test2 to fail as it was expecting the boolean to be false. Changing to @Spy Class1 resulted in the boolean being reset to false and both tests passing.