I have created a springboot project splitted into three maven modules, the domain layer, the core layer (contains persistence and business logic) and the web layer. I try to unit test my repository ProductRepository (located in the core layer)
#RunWith(SpringRunner.class) // provide bridge between SpringBoot test features and JUnit, for usage of springboot tsting features
#DataJpaTest
class ProductRepositoryTest {
#Autowired
private TestEntityManager em;
#Autowired
private ProductRepository repository;
#Test
void shouldReturnProduct() {
// given
Product p = Product.builder().id(1).designation("Test").reference("TEST").unitPrice(150).build();
this.em.persistAndFlush(p);
// when
Product found = repository.findByReference(p.getReference());
// then
assertThat(found.getReference()).isEqualTo(p.getReference());
}
}
But the repository is always instanciated to null. I run this test as JUnit Test in eclipse and i got a nullpointerexception.
Here is my pom.xml file
<dependencies>
<!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId>
</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
You say you try to unit test the controller but you use #RunWith(SpringRunner.class), this is used for integration tests. This annotation starts the complete application. And you just want to test the repository. What you can do is create an abstract DAO which you can implement in your unit tests.
public abstract class AbstractRepository<RepositoryType> {
RepositoryType repository;
private DBI dbi;
protected abstract RepositoryType createRepository(final DBI dbi);
#Before
public final void setUpDataSource() throws Exception {
final JdbcDataSource jdbcDataSource = new JdbcDataSource();
// DB_CLOSE_DELAY=-1 ==> h2 will keep its content as long as the vm lives otherwise the content of the database
// is lost at the moment the last connection is closed.
jdbcDataSource
.setURL("jdbc:h2:mem:play;MODE=MySQL;DB_CLOSE_DELAY=-1L;INIT=RUNSCRIPT FROM 'classpath:path/to/file/init_test.sql';");
final Flyway flyway = new Flyway();
flyway.setDataSource(jdbcDataSource);
flyway.setLocations("/path/to/locations");
flyway.migrate();
dbi = new DBI(jdbcDataSource);
runDbScript("/data.sql");
repository = createRepository(dbi);
}
private void runDbScript(final String scriptPath) throws Exception {
try (InputStreamReader reader = new InputStreamReader(AbstractDaoTest.class.getResourceAsStream(scriptPath),
Charsets.UTF_8); Handle h = dbi.open()) {
RunScript.execute(h.getConnection(), reader);
}
}
}
Now you can overwrite the createRepository method in your test class.
public class ProductRepositoryTest() {
#Override
protected ProductRepository createRepository(Dbi dbi) { return new ProductRepository(dbi); }
#Test
public void testGetProductById() {
Product response = repository.getProductById(1);
assertThat(response).isEqualTo(someObject);
}
}
If you need a framework to mock objects you can use Mockito and if you need to mock static or void methods you can use PowerMock.
Hope this helps.
Add these annotations to your test classes.
#SpringBootTest
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DbUnitTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class YourTestClass {....
here's a working version of your example - hope this helps. I think you may have some conflicting configuration or dependencies in your own project. https://github.com/tndavidson/springbootjparepositorytest
Related
I am using Spring Data Mongodb and Embeded mongoDB to persist the data.
Pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
applicaiton.properties
spring.data.mongodb.port=2019
spring.data.mongodb.database=testdb
spring.data.mongodb.host=localhost
Main class:
#SpringBootApplication
#EnableAutoConfiguration
#EnableMongoRepositories(basePackages = { "com.koka.mongotest.repo"})
public class MongotestApplication {
public static void main(String[] args) {
SpringApplication.run(MongotestApplication.class, args);
}
}
Enity or Domain class :
#Document
public class Person
{
#Indexed
private String personId;
private String name;
//getter and setters
}
Repo:
#Repository
public interface PersonRepo extends MongoRepository { //No Custom emthod}
Service layer :
#Service
public class ServiceImpl {
#Autowired
PresonRepo repo;
public void saveJon(Person p )
{
repo.save(p);
}
}
but Int DB its getting saved as
{"_id":{"$oid":"60e18f9d7eb50d70b56b543f"},"_class":"com.koka.mongotest.entity.Person"}
But that person Object hold info which is not being saved. Please advise on this,
I am trying to test one test case using controller class with service interface and interface impl class. But it's always failing and returning either null pointer exception or not found custom exception even mocking the method to return mock values.
Controller.class
#RestController
public class RestAPIController {
#Autowired
RestAPIService restAPIService;
#PostMapping(path = "list/{id}")
public ResponseEntity<List<Employee>> findByID(#PathVariable(value = "id") String id)
throws RestAPIException {
List<Employee> list = restAPIService.findByID(id);
if (list == null || list.isEmpty()) {
throw new IDNotFoundException(id);
}
return new ResponseEntity<>(list, HttpStatus.OK);
}
Service Interface:
#Service
public interface RestAPIService {
public List<Employee> findByID(#Param("id") String id) throws RestAPIException;
}
Service impl class:
#Service
public class RestAPIServiceImpl implements RestAPIService {
#Override
public List<Employee> findByID(String id) throws RestAPIException {
return serviceUtils.transformationObj(repository.findByID(id));
}
}
Test Class
#Mock
RestAPIService restAPIServiceImpl;
#InjectMocks
RestAPIController restAPIController;
#Test
public void testListByIDController() throws RestAPIException {
Mockito.when(restAPIServiceImpl.findByID("1")).thenReturn(baseDTOData());
ResponseEntity<List<Employee>> expectedList = restAPIController.findByID("1");
assertEquals(1, expectedList.getBody().size());
}
private List<Employee> baseDTOData() {
List<Employee> list = new ArrayList<>();
Employee e = new Employee();
e.setId("2");
list.add(e);
return list;
}
pom.xml (using JUnit5)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito extention -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
The above code is failing and enters into IDNotFoundException but mock return is not working.
Can anyone please help on this.
I have created a new Spring Boot application using Spring Data.
I got an issue when I try to test my JpaRepository:
[ERROR] Failures:
[ERROR] EmployeeRepositoryTest.testDeleteEmployee:46 NullPointer
[ERROR] EmployeeRepositoryTest.testGetEmployee:37 NullPointer
[ERROR] EmployeeRepositoryTest.testSaveEmployee ยป Test
Expected exception of type cla...
java.lang.NullPointerException in the line
repository.save(employee);
where a repository has been defined as:
#Autowired
private EmployeeRepository repository;
Follow my pom.xml and test class:
#ExtendWith({SpringExtension.class})
#DataJpaTest
class EmployeeRepositoryTest {
#Autowired
private EmployeeRepository repository;
#Test(expectedExceptions = ResourceNotFoundException.class)
void testSaveEmployee() throws ResourceNotFoundException {
Employee employee = new Employee("John", "Smith", "john.smith#email.com");
repository.save(employee);
employee = repository.findById(employee.getId()).orElseThrow(() -> new ResourceNotFoundException(""));
assertNotNull(employee);
assertEquals(employee.getFirstName(), "John", "The employee firstname should be John");
}
}
...
<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-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
...
Could you please help to understand which is the issue?
Below the EmployeeRepository interface:
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
}
I'm using Spring Boot 1.5.9 with spring-boot-starter-data-jpa and Camel 2.20.1.
As input I get an XML file with a sequence of elements, which I unmarshal with JAXB and after that I aggregate them to a list of elements.
For that I defined a Element class where I associate a root element for JAXB and annotated it as a JPA entity.
#Entity
#Table (name="tblElement", schema="comp")
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Element implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column (name="name")
#XmlElement(required = true)
private String name;
public void Element(){}
//getter and setter methods
...
}
So my camel route processes a list of entries which I want to store to a MySQL database (version: 5.1.73).
#Component
public class CompRoute extends RouteBuilder {
#Autowired
private ElementDao elementDao;
#Override
public void configure() throws Exception {
DataFormat jaxbDataFormat = new JaxbDataFormat("com.comp.beans");
from(file:src/test/resources/input)
.convertBodyTo(byte[].class, "UTF-8")
.split().tokenizeXML("element")
.unmarshal(jaxbDataFormat)
.setProperty("SplitSize", simple("${header.CamelSplitSize}"))
.aggregate(constant(true), new ArrayListAggregationStrategy())
.completionSize(simple("${property.SplitSize}"))
.bean(elementDao, "insertElementList");
}
}
I'm not so familiar with JPA and the transaction manager, so I configured it according these documentations:
https://spring.io/guides/gs/accessing-data-jpa/
https://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html
application.properties
spring.datasource.url=jdbc:mysql://compserver:3306/comp
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
JpaConfig.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = CompApplication.class)
class JpaConfig {
#Autowired
DataSource dataSource;
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
String entities = ClassUtils.getPackageName(CompApplication.class);
factory.setPackagesToScan(entities);
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
#Qualifier (value = "jpaTransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
CompApplication.java
#SpringBootApplication
public class CompApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(CompApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
ElementDao.java
#Service
public class ElementDao {
#Autowired
ElementRepository elementRepository;
#Transactional (transactionManager = "jpaTransactionManager", readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void insertElementList(Exchange exchange) {
if(exchange.getIn().getBody() instanceof List) {
List<Element> elements= convertListToElementList(exchange.getIn().getBody(List.class));
if (elements != null) {
elementRepository.save(elements);
}
}
}
}
ElementRepository.java
#Repository
public interface ElementRepository extends CrudRepository<Element, Long> {
}
But my transaction configuration is not working correct. Because if an error occured e.g. while storing the 5th element, the whole transaction is not rolled back. It should not insert any elements. But still the first 4 elements are stored and commited.
I don't understand this behavor? How can I set my service #insertElementList transactional that the whole operation is rolled back when an exception occured during database storage?
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>company</artifactId>
<version>0.0.1</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<camel.version>2.20.1</camel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jdbc</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jaxb</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jms</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>com.ibm</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>9.0.0.1</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>4.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
</project>
I found the solution. The problem was the database and not my configuration. The MySql table was of type MyISAM, which does not support transactions and roll back. So I converted the table to InnoDB and now it works - when the transaction fails, all is rolled back.
According to Spring documentation.
#Transactional on beans in the same application context it is defined in. This means that, if you put annotation in a WebApplicationContext for a DispatcherServlet, it only checks for #Transactional beans in your controllers, and not your services.
So, your save method is not transactional , include that package also in which
save method is defined.
Have a class in the package com.conf
#Configuration
public class WebConfTest {
#Autowired
private Environment environment;
}
and unit test into com.service
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { WebConfTest.class })
public class DMSServiceImplTest {
#Autowired
WebConfTest webConfTest;
#Test
public void testConnect() throws Exception {
}
}
test dependency :
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
In the IDEA navigation between beans work. But WebConfTest== null if I run test.
What is wrong?
Thanks.
#RunWith is for junit runner.
If you want to run tests with TestNG, you need to extends AbstractTestNGSpringContextTests.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#testcontext-support-classes-testng