@SpyBean Example in Spring Test

By Arvind Rai, April 23, 2021
On this page we will learn using @SpyBean annotation in Spring Boot unit test cases. Let us understand @SpyBean point-by-point.
1. The @SpyBean is a Spring Boot test annotation that is used to add Mockito spies to ApplicationContext.
2. Spies can be applied by type or bean name.
3. All existing beans of the same type defined in the context will be wrapped with spy and if no existing bean then new one will be added to context.
4. The@SpyBean can be used at field level and class level in test classes or @Configuration classes.
5. If a registered bean in application context is spied then injection of this bean on field is also spied.
6. The test class using @SpyBean, can be annotated with
@RunWith(SpringRunner.class) 
Or
@ExtendWith(SpringExtension.class) 

7. The @SpyBean has following attributes.
classes: Classes to spy.
name: Name of the bean to spy.
proxyTargetAware: Boolean value. If true then Mockito methods such as verify(mock) should use target of AOP advised beans, rather than the proxy itself.
reset: The MockReset mode.
value: Alias of classes i.e. the classes to spy.

Technologies Used

Find the technologies being used in our example.
1. Java 14
2. Spring 5.3.6
3. Spring Boot 2.4.5
4. JUnit 5.7.1
5. Mockito 3.6.28
6. Maven 3.8.1

Maven Dependencies

Find the Maven dependencies.
pom.xml
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.4.5</version>
</parent>
<properties>
	<context.path>spring-app</context.path>
	<java.version>14</java.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
	</dependency>	
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-api</artifactId>
		<version>5.7.1</version>
		<scope>test</scope>
	</dependency>	
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-engine</artifactId>
		<version>5.7.1</version>
		<scope>test</scope>
	</dependency>	
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-params</artifactId>
		<version>5.7.1</version>
		<scope>test</scope>
	</dependency>		    
	<dependency>
		<groupId>org.junit.platform</groupId>
		<artifactId>junit-platform-launcher</artifactId>
		<version>1.7.1</version>
		<scope>test</scope>
	</dependency>	    	    		
</dependencies> 

@MockBean vs @SpyBean

1. The @MockBean and @SpyBean both are the Spring Boot test annotations.
2. The @MockBean annotation is used to apply Mockito mocks whereas @SpyBean annotation is used to apply Mockito spies.
3. When we mock an object of a class, we get an empty object and not the actual object. All the methods of mocked object return null and all the field values are also null.
Find the sample code.
MockitoMockTest.java
@ExtendWith(SpringExtension.class)
public class MockitoMockTest {
  @MockBean
  private Person person;

  @Test
  public void test1() {
	String name = person.getName();
	assertEquals(null, name);
  }

  @Test
  public void test2() {
	Mockito.when(person.getAge()).thenReturn(20);
	int age = person.getAge();
	assertEquals(20, age);
  }
}

class Person {
  public String getName() {
	return "Shiva";
  }

  public int getAge() {
	return 30;
  }
} 
In the test1 method, we can see that getName() is returning null instead of actual value.
4. The spy object is a wrapper around an actual object of a class. The methods and fields of spy object keep actual values except of those which we build the stub.
Find the sample code.
MockitoSpyTest.java
@ExtendWith(SpringExtension.class)
public class MockitoSpyTest {
  @SpyBean
  private Person person;

  @Test
  public void test1() {
	String name = person.getName();
	assertEquals("Shiva", name);
  }

  @Test
  public void test2() {
	Mockito.when(person.getAge()).thenReturn(20);
	int age = person.getAge();
	assertEquals(20, age);
  }
} 
In the test1 method, we can see that getName() is returning actual value but not null.

Spy at Field Level

Here we will use @SpyBean at field level. In this case @Autowired will not be annotated for dependency injection.
MyAppTest1.java
package com.concretepage;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.concretepage.config.AppConfig;
import com.concretepage.service.MyService1;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class MyAppTest1 {
    @SpyBean 
    private MyService1 myService;
    @Test
    public void testApp1() {
        Mockito.when(myService.getMessage()).thenReturn("Welcome");
        String msg = myService.getMessage();
        assertEquals("Welcome", msg);
    }	 
} 
If a service class is using a bean which has been spied in our test class, then that service will receive spied bean for our test.
MyAppTest1.java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class MyAppTest1 {
    @SpyBean 
    private MyService1 myService1;
    
    @SpyBean   
    private MyService2 myService2;   
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testApp1() {
        Mockito.when(myService1.getMessage()).thenReturn("Welcome");
        String msg = myService1.getMessage();
        assertEquals("Welcome", msg);
    }	 
    
    @Test
    public void testApp2() {
        Mockito.when(myService2.getCount()).thenReturn(100);
        int count = userService.getUserCount();
        assertEquals(100, count);
    }    
} 

Spy at Class Level

The @SpyBean has attributes classes and value i.e. alias of classes. We annotate test class with @SpyBean and configure classes to spy.
More than one classes at class level, are configured in following ways.
1. Using array of classes.
@ExtendWith(SpringExtension.class)
@SpyBean({MyService1.class, MyService2.class})
public class MyAppTest2 {
------
} 
2. Using repeated @SpyBean.
@ExtendWith(SpringExtension.class)
@SpyBean(MyService1.class)
@SpyBean(MyService2.class)
public class MyAppTest2 {
------
} 
3. Using @SpyBeans. The @SpyBeans is the container annotation that aggregates several @SpyBean annotations.
@ExtendWith(SpringExtension.class)
@SpyBeans({
  @SpyBean(MyService1.class),
  @SpyBean(MyService2.class)
})
public class MyAppTest2 {
------
} 

Find a complete test class with @SpyBean annotated at class level.
MyAppTest2.java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
@SpyBeans({
  @SpyBean(MyService1.class),
  @SpyBean(MyService2.class)
})
public class MyAppTest2 {
    @Autowired
    private MyService1 myService1;
    
    @Autowired  
    private MyService2 myService2;    
    
    @Test
    public void testApp1() {
        Mockito.when(myService1.getMessage()).thenReturn("Welcome");
        String msg = myService1.getMessage();
        assertEquals("Welcome", msg);
    }	 
    
    @Test
    public void testApp2() {
        Mockito.when(myService2.getCount()).thenReturn(100);
        int count = myService2.getCount();
        assertEquals(100, count);
    }    
} 

Spy using @Configuration

The classes can be spied in a @Configuration annotated class using @SpyBean annotation either at field level or class level.
MyAppTest3.java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppTestConfig.class)
public class MyAppTest3 {
    @Autowired
    private MyService1 myService1;
    
    @Autowired  
    private MyService2 myService2;    
    
    @Test
    public void testApp1() {
        Mockito.when(myService1.getMessage()).thenReturn("Welcome");
        String msg = myService1.getMessage();
        assertEquals("Welcome", msg);
    }	 
    
    @Test
    public void testApp2() {
        Mockito.when(myService2.getCount()).thenReturn(100);
        int count = myService2.getCount();
        assertEquals(100, count);
    }    
}

@Configuration
@SpyBeans({
  @SpyBean(MyService1.class),
  @SpyBean(MyService2.class)
})
class AppTestConfig {
} 
We can see that two service classes have been spied in configuration class at class level. In our test class the spy classes will be injected using @Autowired annotation.
Find the code snippet to use @SpyBean at field level in configuration class.
@Configuration
class AppTestConfig {
  @SpyBean
  private MyService1 myService1;
  
  @SpyBean  
  private MyService2 myService2;  
} 

@SpyBean with @Qualifier

If an interface has more than one implementation class then to choose the exact class for dependency injection, @Qualifier annotation is used. Here we will show the demo to use @SpyBean with @Qualifier annotation.
MyAppTest4.java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class MyAppTest4 {
  @SpyBean
  @Qualifier("deer")
  private Animal animal1;
  
  @SpyBean
  @Qualifier("fox")
  private Animal animal2;

  @Test
  public void testApp1() {
	Mockito.when(animal1.getName()).thenReturn("xxx");
	assertEquals("xxx", animal1.getName());
  }

  @Test
  public void testApp2() {
	Mockito.when(animal2.getName()).thenReturn("yyy");
	assertEquals("yyy", animal2.getName());
  }
} 
Find the print screen of the output.
@SpyBean Example in Spring Test

References

Annotation Type SpyBean
Annotation Type SpyBeans

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us