SpringBoot: Running a Junit unit test with minimal configuration - java

Im having a Springboot project where I have found a way to create and run simple Junit testcase which looks into a repository and fetches some data attribute for a given entity. The result of the Junit run is pass so no problem in regards to that.
But the thing is here, that I have seen a lot of examples out there where tutorials are showing Springboot projects where they can simply run Junit tests with only #Runwith or #SpringBootTest
for their specific test classes.
In my case I have to add 3 annotations, #SpringBootTest, #RunWith as well as #ContextConfiguation(with parameters) until Im able to run the testcase.
So my question is how will I be able to run it as minimalistic as possible, (some exercises I have seen have only one annotation for their springboot test class)
My Springboot test class looks like this:
Screenshot of my Junit class
and my Directory structure looks like this:
Screenshot of my Project directory structure
My application.properties looks like this:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.show-sql=true
spring.datasource.url=jdbc:postgresql://localhost:5432/erfan
spring.datasource.username=erfan
spring.datasource.password=
#Some additional properties is trying to be set by Spring framework so this must be set
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
#spring.datasource.initialization-mode=always
#spring.datasource.initialize=true
#spring.datasource.schema=classpath:/schema.sql
#spring.datasource.continue-on-error=true
#HikariCP is a ConnectionPool manager, related to DB stuff
#Below is the property key you need to set to * as value to expose all kind of monitoring related information
#about your web application
#management.endpoints.web.exposure.include=*
And my pom.xml file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<groupId>com.sample</groupId>
<artifactId>postgres</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>postgres</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
So am I missing like something in my application.properties file? Something that I should include to be able to remove "boilerplate" annotation in my test class?

Depends on what you're trying to do. Basically spring has custom annotations that configures the spring context to include only relevant beans. This is the so called test slices.
But there are a few "rules" I always try to follow:
Avoid #SpringBootTest unless you're doing integration testing, or manually setting which classes to use #SpringBootTest(classes = {MyService1.class, MyService2.class}
If you're testing spring jpa, you can use the #DataJpaTest annotation, example here
If you're testing controllers you can use the #WebMvcTest, example here
If you're testing other services, you can always use #ContextConfiguration to configure the spring context accordingly.
So for example, for your test I would write it in one of two ways:
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(AnotherConfig.class)
class MyTest {
// test stuff here
}
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {AnotherConfig.class})
// NOTE, if you have a JpaConfig class where you #EnableJpaRepositories
// you can instead add this config class to the #ContextConfiguration classes
#EnableJpaRepositories
class MyTest {
// test stuff here
}
Basically, don't worry about how many annotations you have on top of your test, but worry about which beans/services are being autowired. For example the #SpringBootTest is a single annotation, but autowires all the beans in the spring context.

I strongly recommend not using a bunch of spring annotations on unit tests. Unit tests should only test one piece of code and not relate with externals or other layers, so Mockito should be sufficient.
Example:
#RunWith(MockitoJUnitRunner.class)
public class FooTest {
#InjectMocks
private FooService service;
#Mock
private FooRepository repository;
#Test
public void whenHappyPath_shouldReturnTrue(){
doReturn(Optional.empty()).when(repository).findById(anyLong());
assertTrue(service.isFoo(1L));
}
}
You are preventing your unit test to reach the repository layer, so you don't need to create a context with embedded DB or any other thing.
If you are using for integration tests then it is different and you will need different strategies. For that, I'd recommend use embedded DB on tests (which is made by default if you have h2 dependency):
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
And also use a integration or test spring profile:
#ActiveProfile("test") // or integration, you choose
public class FooIntegrationTest{
...
}
or force other configuration file to point to another configuration
#TestPropertySource(properties = { "spring.config.location=classpath:application-test.yml" })
application-test.properties
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop

Erfan, it completely depends on your test scenario.
First Scenario : Complete test (Integration Test)
If you want to test the whole of your app, like testing the Service layer, Repository layer, and the Controller layer, you need a real spring-context, so you must use all #SpringBootTest and #RunWith and ... to initialize spring context to test whole layers.
(This called integration-test)
Unit Test vs Integration Test: What's the Difference
how-to-use-java-integration-testing
Second Scenario: Unit test
If you want just to test a piece of your code, just like you want to test just service layer and other layers (like repository) does not important in your scenario, in this situation you must use some new framework like Mockito, to mock the pieces that you don't want to test them, in these scenarios you don't need the **spring-context initialization ** so you don't need to use #SpringBootTest or other annotations.
Mockito Sample
So based on your scenario you can use those annotations.
I strongly recommend you to read the below link for further information about best practices for testing in java.
Modern Best Practices for Testing in Java

Related

Spring Boot Unit test to test Cassandra throwing Configuration Exception

I have created the below class under test folder to unit test the cassandra connection in my spring boot application
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest({ "spring.data.cassandra.port=9042",
"spring.data.cassandra.keyspace-name=keyspacename","spring.data.cassandra.concontact-points=url",
"spring.config.import=vaultpath"})
#EnableAutoConfiguration
#ContextConfiguration
#ComponentScan
#TestExecutionListeners({ CassandraUnitDependencyInjectionTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class })
#CassandraUnit
public class ApplicationTests {
#Autowired
TestRepository testRepository;
#Test
public void contextLoads(){
System.out.println(test.findBytestId("***"));
}
}
Below are the dependencies added in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit-spring</artifactId>
<version>2.1.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Getting the below error on trying to run the unit test not sure what other config needs to be added in test path
Expecting URI in variable: [cassandra.config]. Please prefix the file with file:/// for local files or file:/// for remote files. Aborting. If you are executing this from an external tool, it needs to set Config.setClientMode(true) to avoid loading configuration.
at org.cassandraunit.utils.EmbeddedCassandraServerHelper.copy(EmbeddedCassandraServerHelper.java:278)
at org.cassandraunit.utils.EmbeddedCassandraServerHelper.startEmbeddedCassandra(EmbeddedCassandraServerHelper.java:82)
at org.cassandraunit.utils.EmbeddedCassandraServerHelper.startEmbeddedCassandra(EmbeddedCassandraServerHelper.java:64)
Fatal configuration error; unable to start. See log for stacktrace.

Why custom JDBC driver doesn't work in integration tests with Spring?

I'm making custom JDBC driver and I need to test it (with Spring-boot). I added a dependency (I use Maven) this driver JAR to another project and it's work fine in the main program. But when I'm trying to make integration tests (in unit tests the driver registered in DriverManager by itself) driver doesn't load so Spring-boot can't create bean with name 'dataSource'.
I also tried to write unit tests to use the driver directly, but the driver cannot load the properties file that is contained in the resources (file path is src/main/resources/driver.properties). In this case the driver registered in DriverManager by itself.
How can I make it so that:
The driver can see the file from resources during unit tests? FIXED
The driver start register itself during integration tests with Spring?
Note: JDBC use the service provider mechanism to load drivers, so I also have the java.sql.Driver file in resources.(src/main/resources/META-INF/services/java.sql.Driver).
Link for the JDBC driver code. Readme have also link to project, where I'm trying to test driver
Code of test:
#SpringBootTest
public class ApplicationSmokeTest {
#Test
public void contextLoad(){
assertTrue(true);
}
}
In main program driver is loaded and spring work with them fine.
Dependencies:
<dependencies>
<dependency>
<groupId>org.mock.jdbc</groupId>
<artifactId>HttpDriver</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>

Simple Spring Boot Project is giving 404 Error

I am learning Spring Boot but I am not able to execute the basic "Hello World" program. I have built the basic project using https://start.spring.io/ and updated the code as per below code snippets:
Spring Boot Main Class:
package com.rohit.springboot.practice.selfspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan(basePackages = "com.rohit.springboot.practice.selfspringboot")
public class SelfSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(SelfSpringBootApplication.class, args);
}
}
Controller Class:
package com.rohit.springboot.practice.selfspringboot;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ThisWorldSpringContainer {
#RequestMapping(method = RequestMethod.GET, value = "/hello-world")
public String getWelcome() {
return "Hello World";
}
}
POM.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rohit.springboot.practice</groupId>
<artifactId>self-spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>self-spring-boot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Error:
Message: The requested resource [/hello-world] is not available
I know this question has been asked by multiple people but i have gone through those answers and tried those given answers but didn't work.
Technologies used are Java 8, Tomcat 9, Spring Boot 2.6.6 etc.
Kindly help.
Since you don't have the Tomcat dependency, I'm implying that you want your application to run in a standalone Tomcat instance. Also, given your comment, I believe you're working on either Eclipse or Spring Tools Suite (Which is the same, really).
With that, it seems your problem is the lack of a packaging method. Tomcat needs the code its going to run in a package. This package is a zip-like file with .war extension. Maven, the program that handles the dependencies of your project according to the pom.xml file, can package it in either war of jar format. Since you are running it in an external Tomcat, you want the war extension. So please, add the following line to your pom.xml file. It has to be in the same level as <name>:
<packaging>war</packaging>
Then, update your project (Alt + F5, check your project and hit OK) then try running your project again. You should see an stylish "SPRING" output in your console.
Now, since you are running this externally, you need to specify the name of the project, so you should call your endpoint with
http://localhost:8080/<your_project_name_here>/hello-world
I advise you to check this new change in Spring Boot 2.6 :
PathPattern Based Path Matching Strategy for Spring MVC
In this release, the default strategy for matching request paths against registered Spring MVC handler mappings has changed from AntPathMatcher to PathPatternParser.
If you need to switch the default strategy back to AntPathMatcher, you can set the property spring.mvc.pathmatch.matching-strategy to ant-path-matcher as in the line below to add in the file application.properties :
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
I encountered the same issue of HTTP 404 error when upgrading to Spring Boot 2.6 and adding this property solved my problem and should solve yours.
I have modified your code as below:
#RequestMapping
#RestController
public class ThisWorldSpringContainer {
#GetMapping(value = "/hello-world")
public String getWelcome() {
return "Hello World";
}
}
I thinking you are missing
#Requestmapping
in classlevel

Using Simple Logging Facade for Java n a SpringBoot app

I want to use the #Slf4j annotation, so I imported this dependency in my pom.xml file
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.29</version>
</dependency>
but I have the error Cannot resolve symbol Slf4j
#Service
#Slf4j
#Transactional(readOnly = true)
public class PasswordResetTokenService {
..
}
The annotation #Slf4j is a Lombok annotation and is not present in the slf4j dependency.
If you want to use this annotation instead of declaring a logger field, you will need to add an extra dependency to Lombok:
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
In case of Spring Boot, the parent POM might already specify the version. Then you don't need to declare the specific version anymore.
See:
https://projectlombok.org/features/log
https://projectlombok.org/api/lombok/extern/slf4j/Slf4j.html
I believe #Slf4j annotation is not actually coming from Slf4j but from Lombok. please look at this link that seems to provide a very good template to start from https://howtodoinjava.com/spring-boot2/logging/logging-with-lombok/
if you look at the excerpt of Application.java. the import for the annotation is coming from lombok
import lombok.extern.slf4j.Slf4j;

Vaadin problem in Spring Boot multi module project

I made up an example spring boot project running with Vaadin (latest version). I only have one view:
#Route
public class MainView extends VerticalLayout {
The UI was working like a charm, then I had to refactor the project in modules.
I put the SpringBootApplication in a module and Vaadin in another one. I'm getting into modules, so I don't know exactly how they interact, but I had to put the dependency in the boot pom to the vaadin pom in order to let it start.
Now it is not working, when I call localhost it says
Could not navigate to ''
Reason: Couldn't find route for ''
Available routes:
This detailed message is only shown when running in
development mode.
The spring boot application:
#SpringBootApplication
#ComponentScan(basePackages = {"my.app"})
#EntityScan(basePackages = {"my.app"})
#EnableJpaRepositories(basePackages = {"my.app"})
#EnableJpaAuditing
public class LicensemanagerApplication
boot module pom.xml dependency snippet:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>app_frontend</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
app_frontend module pom.xml dependency snippet:
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
.............
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>13.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Vaadin by default only looks for #Route annotated classes within the same package that contains the #SpringBootApplication annotation. To make it look in other packages, you need to pass those as the value to the #EnableVaadin annotation, e.g. #EnableVaadin({"my.app"}).

Categories