I am new to the concept of unit testing with Spring controllers. I'm following some examples I found online and trying to implement their testing strategy. This is my basic controller:
#Controller
public class GreetingController {
#RequestMapping("/greeting")
public String greeting(#RequestParam(value = "name2", required = false, defaultValue = "World2") String name2,
Model model) {
model.addAttribute("name", name2);
return "greeting";
}
}
This is my unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class ControllerGreetingTest {
private MockMvc mockMvc;
#Autowired
GreetingController greetingController;
#Test
public void shouldReturnSomething() throws Exception {
mockMvc.perform(get("/greeting"))
.andExpect(status().isOk())
.andExpect(view().name("greeting"));
}
}
Seems pretty straight forward but I get the following error:
java.lang.IllegalStateException: Neither GenericXmlWebContextLoader
nor AnnotationConfigWebContextLoader was able to detect defaults, and
no ApplicationContextInitializers were declared for context
configuration [ContextConfigurationAttributes#1698539 declaringClass =
'com.practice.demo.ControllerGreetingTest', locations = '{}', classes
= '{}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass =
'org.springframework.test.context.ContextLoader']
I'm assuming I have to add a parameter to the #ContextConfiguration annotation but not sure what to include in there.
EDIT = This is what I have so far:
public class ControllerGreetingTest {
private MockMvc mockMvc;
#Before
public void setup(){
this.mockMvc = standaloneSetup(new GreetingController()).build();
}
#Test
public void shouldReturnDefaultString() throws Exception {
mockMvc.perform(get("/greeting"))
.andExpect(status().isOk())
.andExpect(view().name("greetings"))
.andExpect(model().attribute("name","World2"));
}
}
It does the job but it doesn't use any of the Spring annotations like I tried to do before.. this approach is not good so trying to figure out why I keep gettings errors whenever I include the annotations in my test file.
My POM:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.7.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.7.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
You should use spring-boot-starter-test dependency. It has almost everything for testing.
And for testing controller part of a spring application, you should use #WebMvcTest annotation for your test class. With this annotation spring will load context just for controller part. Plus you don't need to setup method if you use this annotation. You can simply autowire mockMvc. Your test class should be like this:
#RunWith(SpringRunner.class)
#WebMvcTest(GreetingController.class)
public class ControllerGreetingTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SomeServiceClass someServiceClass;
#Test
public void shouldReturnDefaultString() throws Exception {
mockMvc.perform(get("/greeting"))
.andExpect(status().isOk())
.andExpect(view().name("greetings"))
.andExpect(model().attribute("name","World2"));
}
}
Note: Your controller does not have any autowired fields. In cases that controller has some autowired objects like service or repository objects. you can simply mock them with annotation #MockBean as you can see above code.
See this link for other test slice annotations spring provided
For projects with org.springframework.boot:spring-boot-starter-test can use
#RunWith(SpringRunner.class)
#SpringBootTest(classes = App.class)
public class ControllerGreetingTest {
...
}
Where App.class is you main application class annotated with #SpringBootApplication. But you better read the documentation. And if you don't want to include (classes = App.class) part you also can change folder structure
For simple controllers it is possible to perform simple standalone tests
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = ControllerGreeting.class)
public class ControllerGreetingTest {
#Autowired
private MockMvc mockMvc;
...
}
Just add the #ContextConfiguration annotation and refer one or more XML configuration file locations or one or more configuration classes. Otherwise Spring cannot autowire your controller, which should be tested.
Example: You want to test a controller, which uses MyService via #Autowired:
MyControllerTest: Injects the controller, which should be tested using the MyTestConfig configuration class.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MyTestConfig.class})
#WebAppConfiguration
public class MyControllerTest {
private MockMvc mockMvc;
#Autowired
private MyController controller;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
// Tests
}
MyTestConfig: Returns all beans, which are required for the test. Use Mockito to mock the depedencies of your controller, because we want to test only the controller and not the service layer.
#Configuration
public class MyTestConfig {
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
#Bean
public MyController myController() {
return new MyController();
}
}
Related
I'm looking for a unit test for Controllers using Mockito and PowerMock. Every controller has only private void methods with a single argument and a #Autowired service dependency:
public class ProjectController {
private ProjectServiceImpl service;
#Autowired
public void setInjectedBean(ProjectServiceImpl service) {
this.service = service;
}
private void createProject(String someString) {
// do stuff by calling service.doSomething(someString)
}
}
ProjectServiceImpl is annotated with #Service and has only a public void method with a String as argument. As it is too simple, I didn't bother providing the code.
My last attempt to accomplish that test looks like this:
#RunWith(PowerMockRunner.class)
#ExtendWith(MockitoExtension.class)
#PrepareForTest({ProjectController.class, ProjectServiceImpl.class})
class ProjectControllerTest {
#InjectMocks
private ProjectController controller;
#Mock
private ProjectServiceImplservice;
#Test
void createProject() throws Exception {
controller = PowerMockito.spy(new ProjectController());
PowerMockito.doNothing().when(controller, "createProject", new String(""));
}
}
Question 1: is it possible to make PowerMock work aside of Mockito?
Question 2: using PowerMock, what's the most appropriate way of calling for a private void method with argument?
IMPORTANT: Before marking this post as a duplicate, I personally challenge you finding any content how to do so. It seems like there is just no content showing not only how to make both work together, but make PowerMockito calls for a private void method with one or more arguments.
dependencies used for PowerMock:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
there's a problem here!
In my application I have a Consumer (kafka) that uses a Facade that uses a Client that calls a rest Api with RestTemplate
I want (have) to test the whole flow excluded the rest call. so I've tried to mock RestTemplate but without success -_-
here an example of my classes
#Service
public class KafkaConsumer{
#Autowired MyFacade facade;
#KafkaListener
public void onMessage(){
facade.awesomeMethod();
}
}
#Service
public class MyFacade{
#Autowired MyClient client;
public void awesomeMethod(){
client.callApi();
}
}
#Service
public class MyClient{
#Autowired RestTemplate restTemplate;
public void callApi(){
restTemplate.exchange(.....);
}
}
now I'm tring to build a test with mock but something goes wrong and RestTemplate call real server
here test class
#ActiveProfile("test")
#RunWith(SpringRunner.class)
#SpringbootTest
#EmbeddedKafka
public class MyFailTest{
#Mock RestTemplate restTemplate;
#InjectMocks MyFacade facade;
#InjectMocks MyClient client;
#InjectMocks KafkaConsumer consumer;
#Autowired KafkaProducer producer;
#Test
public void test(){
Mokito.when(restTemplate.exchange(....)).thenReturn(new ResponseEntity<Void>(HttpStatus.OK);)
producer.send("My Kafka Message");
}
}
here my test dependecies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
Junit4 and Springboot 2.6.7
I think I'm failing in #Mock and #InjecMock logic, i've tried some combination of them ; ) but without success
thanks to #geobreze comment I drilled down into #MockBean.
It was a way tried on my thousands tries but I copied some code without really understand what it was coping
At the end I've used #SpyBean because #MockBean mocks all methods class. To semplify implementation I've mocked Client object, exchenge of RestTemplate it is a little bit complex, I didn't want fall in other stupid error.
It is not useful declare all classes chain, so I've removed facade and I've used #autowired annotation rather than #InjectMock
here my test
#ActiveProfile("test")
#RunWith(SpringRunner.class)
#SpringbootTest
#EmbeddedKafka
public class MySuccessfullTest{
#SpyBean MyClient client;
#Autowired KafkaConsumer consumer;
#Autowired KafkaProducer producer;
#Test
public void test(){
MyClientResponse responseObject = MyClientResponse();
Mokito.doReturn(responseObject).when(client).callApi();
producer.send("My Kafka Message");
}
}
I am setting up a new Spring project for some fun learning/tutorial at home and I seem to run into a fairly common issue but I have tried all possible solutions I found on here but with no luck. Basically what I have is as follows:
Controller class:
#RestController
#RequestMapping(value = "/shop")
public class ShopController {
#Autowired
ShopService shopService;
#GetMapping(value = "/{id}")
public #ResponseBody Shop getTestData(#PathVariable String id) {
return shopService.getShopBasedOnId(id);
}
}
Service class:
#Service
public class ShopService {
#Autowired
private ShopRepository shopRepository;
public ShopService(ShopRepository shopRepository){
this.shopRepository = shopRepository;
}
public Shop getShopBasedOnId(String id) {
return shopRepository.findByShopId(id);
}
}
Repository class:
#Repository
public interface ShopRepository extends PagingAndSortingRepository<Shop, String> {
Shop findByShopId(String shopId);
}
Application class:
#SpringBootApplication
#EnableJpaRepositories("com.example.reservations.repository")
public class ReservationsApplication {
public static void main(String[] args) {
SpringApplication.run(ReservationsApplication.class, args);
}
}
Last but not least my pom.xml with the dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>4.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.2.Final</version>
</dependency>
So the error code I am getting is as follows:
Description:
Parameter 0 of constructor in com.example.reservations.services.ShopService required a bean of type 'com.example.reservations.repository.ShopRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.example.reservations.repository.ShopRepository' in your configuration.
My structure of folders is:
main
|_java
|_com.example.reservations
|_controllers
|_ShopController.java
|_repository
|_ShopRepository.java
|_services
|_ShopService.java
|_ReservationsApplication.java
Turns out I had to have a config file for couchbase repository. What I did was follow the following link and now it all compiles fine!
The problem could be #Autowired in ShopService
#Service
public class ShopService {
#Autowired
private ShopRepository shopRepository;
public ShopService(ShopRepository shopRepository){
this.shopRepository = shopRepository;
}
public Shop getShopBasedOnId(String id) {
return shopRepository.findByShopId(id);
}
}`
should be
#Service
public class ShopService {
private ShopRepository shopRepository;
public ShopService(ShopRepository shopRepository){
this.shopRepository = shopRepository;
}
public Shop getShopBasedOnId(String id) {
return shopRepository.findByShopId(id);
}
}
You can also add #Autowired just above the constructor but it is no longer required if there is only one constructor
In my situation, I had spring boot with multiple modules. So I had to the below to make it work.
#SpringBootApplication (scanBasePackages={"com.sample.test"})
#EnableJdbcRepositories(basePackages={"com.sample.test"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
I have one framework using Spring Boot which contains a controller RestController class ,
#RequestMapping("/details")
#RestController
public class DataController {
private KafkaStreams kafkaStreams;
public DataController(KafkaStreams kafkaStreams) {
this.kafkaStreams = kafkaStreams;
}
#Autowired
DataService dataService;
#RequestMapping(value = "getAllDetails", method = RequestMethod.GET)
public boolean getAllDetails(KafkaStreams kafkaStreams) {
return ktableService.getAllDetails(kafkaStreams);
}
}
In my service implementation class I am using this kafkaStreams object to find the details for my different services.
Now I am using this framework as a dependency in my one of the other application where I have a runner class,
import org.apache.kafka.streams.KafkaStreams;
#Component
public class PipelineRunner {
private final StreamsBuilder streamsBuilder;
private final KafkaProperties kafkaProperties;
private final SerdesExt serdesExt;
#Autowired
public PipelineRunner(StreamsBuilder streamsBuilder, KafkaProperties kafkaProperties, SerdesExt serdesExt) {
this.streamsBuilder = streamsBuilder;
this.kafkaProperties = kafkaProperties;
this.serdesExt = serdesExt;
}
#PostConstruct
public void run() {
ReflectData.AllowNull.get().addStringable(Utf8.class);
ReflectData.get().addStringable(Utf8.class);
DataProcessor processor = new DataProcessor(streamsBuilder, kafkaProperties,
serdesExt);
start();
}
private void start() {
KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(),
kafkaProperties.getKafkaStreamsProperties(serdesExt));
System.out.println("----Its is started----");
DataController controller = new DataController(kafkaStreams);
kafkaStreams.start();
}
}
In this class i am trying to create the object for DataController .
So when I am trying to run the application class,
#SpringBootApplication(scanBasePackages = { "framework package" })
#EnableConfigurationProperties(KafkaProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I am getting this error,
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in frameworkpackage.controllers.DataController required a bean of type 'org.apache.kafka.streams.KafkaStreams' that could not be found.
Action:
Consider defining a bean of type 'org.apache.kafka.streams.KafkaStreams' in your configuration.
I am new to Spring Boot. So I might be doing something wrong here. If more info is needed I can provide.
UPDATE
My pom file,
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<properties>
<confluent.version>4.1.0</confluent.version>
<kafka.version>1.1.0</kafka.version>
<lombok.version>1.18.0</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-avro-serializer</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-streams-avro-serde</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>${kafka.version}</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-avro</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
A problem is that you are trying to use a bean kafkaStreams in your class DataController, but there is no bean with this name in Spring context. You need to create it manually, so you can autowire it later.
In your case I would suggest to update PipelineRunner.java like this:
import javax.annotation.PostConstruct;
import org.apache.kafka.streams.KafkaStreams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
#Component
public class PipelineRunner
{
private final StreamsBuilder streamsBuilder;
private final KafkaProperties kafkaProperties;
private final SerdesExt serdesExt;
#Autowired
public PipelineRunner(StreamsBuilder streamsBuilder, KafkaProperties kafkaProperties, SerdesExt serdesExt)
{
this.streamsBuilder = streamsBuilder;
this.kafkaProperties = kafkaProperties;
this.serdesExt = serdesExt;
}
#PostConstruct
public void run()
{
ReflectData.AllowNull.get().addStringable(Utf8.class);
ReflectData.get().addStringable(Utf8.class);
DataProcessor processor = new DataProcessor(streamsBuilder, kafkaProperties,
serdesExt);
start();
}
#Bean
KafkaStreams kafkaStreams()
{
KafkaStreams kafkaStreams = new KafkaStreams(
streamsBuilder.build(),
kafkaProperties.getKafkaStreamsProperties(serdesExt));
System.out.println("----Its is started----");
kafkaStreams.start();
return kafkaStreams;
}
}
You dont need to create an instance of DataController by yourself, this will be done automatically by Spring.
More information about Spring approach to beans is available there
I downgrade my application from spring 4.x to 3.x and now when I fire simple test in spock which using autowired bean, this bean is null.
#ContextConfiguration(classes = Configuration.class)
class SomeTestClass extends Specification {
#Autowired
SomeService someService
def "someService"(){
expect:
someService.returnHelloWorld() == "Hello World" // (<- NullPointer)
}
}
My pom.xml file:
<dependencies>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.12</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.1-groovy-2.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.1.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
After when I downgrade also spock-core/spring to 0.6-groovy-1.8 and groovy-all to 1.8 and fire my test it throws this exception:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'someAnotherBean' defined in file
../SomeBean.class: Instantiation of bean failed; nested exception
isorg.springframework.beans.BeanInstantiationException: Could
notinstantiate bean class [..SomeBean.class]: No default
constructorfound; nested exception is
java.lang.NoSuchMethodException:..SomeBean.()
This bean contains contructor which i used to intizialize final fieds in class:
#Component
#PropertySource("classpath:someproperties.properties")
public class HeaderFactory {
private final SomeObject someObject;
public HeaderFactory(#Value("${someProperty1}") String someProperty1, #Value("${someProperty2}") String someProperty2) {
SomeObject someObject = new SomeObject(someProperty1,someProperty2);
this.someObject = someObject;
}
}
Everything worked pretty well before I dowgraded spring version. Any ideas?
You could create a configuration class and define the problematic beans there:
#Configuration
class MyConfig {
#Value("${someProperty1}") String prop1;
#Value("${someProperty2}") String prop2
#Bean
public SomeBean someBean() {
SomeBean bean = new SomeBean(prop1, prop2);
return bean;
}
}
Documentation here: https://docs.spring.io/spring/docs/3.2.0.RELEASE/spring-framework-reference/html/new-in-3.0.html#new-feature-java-config
I resolved my issue by this way:
#Component
public class UrlBuilder {
private final String host;
private final String port;
private final String protocol;
#Autowired
public UrlBuilder(Environment env) {
this.protocol = env.getRequiredProperty("app.server.protocol").toLowercase();
this.serverHost = env.getRequiredProperty("app.server.host");
this.serverPort = env.getRequiredProperty("app.server.port", Integer.class);
}
}
Source