I'm learning Spring: I've a simple JUnit test based on Spring, but I can't understand why one test is failing and the other not, since I'm expecting the #Autowired annotation to work in both of them
I have 2 classes:
package it.euphoria.data.service;
import it.euphoria.data.entity.Customer;
import it.euphoria.data.entity.Seller;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import javax.validation.constraints.NotNull;
import java.util.Set;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
#Query("SELECT cust FROM Customer cust WHERE cust.seller = :seller")
Set<Customer> getBySeller(#NotNull #Param("seller") Seller seller);
}
and a Service class:
package it.euphoria.data.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class CustomerService {
private CustomerRepository customerRepository;
#Autowired
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public CustomerRepository getCustomerRepository() {
return customerRepository;
}
}
If , while testing, I autowire CustomerRepository it works fine, but if I autowire CustomerService I get an UnsatisfiedDependencyException
This is the working test with CustomerRepository:
package it.euphoria.data.service;
import it.euphoria.data.entity.Customer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DataJpaTest
public class CustomerRepositoryTest {
#Autowired
CustomerRepository customerRepository;
#Test
public void getBySeller() {
//test things...
}
}
And this is the broken one with CustomerService as only difference:
package it.euphoria.data.service;
import it.euphoria.data.entity.Customer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DataJpaTest
public class CustomerRepositoryTest {
#Autowired
CustomerService customerService; //<-- The error is here
#Test
public void getBySeller() {
//test things...
}
}
This is the stacktrace:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name
'it.euphoria.data.service.CustomerRepositoryTest': Unsatisfied
dependency expressed through field 'customerService'; nested exception
is org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'it.euphoria.data.service.CustomerService'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I've found this question and answer , but it didn't solve the problem.
Could you please help me understanding what I'm doing wrong here?
#DataJpaTest loads the beans which are relevant to the work with the database, read Repositories and more low level stuff (DataSources, Transaction manager, etc).
These tests are used to check the correctness of your SQL queries and any code that resides in the DAO.
It doesn't load the whole application in any case but only a certain part of it.
Now the service is a place where you usually write a business logic and it doesn't interact with database directly (only via DAOs, repositories, etc.)
So spring doesn't load services in #DataJpaTest
Related
I'm creating a read only REST API using Spring Boot 2.7 and Java 19
I followed the idea posted here: https://www.baeldung.com/spring-data-read-only-repository
I've created a read only generic repository:
package com.scope.mdm.domain;
import java.util.List;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
List<T> findAll();
}
This is a simplify view of the entity definition:
package com.scope.mdm.domain;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Immutable;
#Entity
#Immutable
#Table(name = "v_LegalEntity", schema = "entity_reference")
public class LegalEntity {
#Id
#Column(name = "LegalEntityId")
Integer legalEntityId;
#Column(name = "SO_Id")
String soId;
#Column(name = "Source_LegalEntityId")
I implemented a read only repository as follows:
package com.scope.mdm.domain;
import java.util.List;
import java.util.Optional;
public interface LegalEntityReadOnlyRepository extends ReadOnlyRepository<LegalEntity, Integer> {
// Fetch legal entities by Scope One Id
Optional<LegalEntity> findBySoId(String soId);
// Fetch legal entities by its data source provided ID
LegalEntity findByLegalEntityId(String legalEntityId);
// Legal entities containing the name
List<LegalEntity> findByLegalEntityNameLike(String legalEntityName);
}
This is the controller:
package com.scope.mdm.web;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import com.scope.mdm.domain.LegalEntity;
import com.scope.mdm.domain.LegalEntityReadOnlyRepository;
#RestController
public class LegalEntityController {
#Autowired
private LegalEntityReadOnlyRepository repository;
#RequestMapping(value="/legalEntities", method=RequestMethod.GET)
public Iterable<LegalEntity> getLegalEntity() {
return repository.findAll();
}
}
I want to do a simple unit test like this:
package com.scope.mdm;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.scope.mdm.domain.LegalEntityReadOnlyRepository;
import com.scope.mdm.domain.LegalEntityRepository;
import org.junit.jupiter.api.Assertions;
#SpringBootTest(classes = mdmWebAPIApplication.class)
public class legalEntitySearchTest {
#Autowired
private LegalEntityRepository repository;
#Autowired
private LegalEntityReadOnlyRepository repositoryReadOnly;
#Test
public void contextLoads() {
Assertions.assertNotNull(repositoryReadOnly);
Assertions.assertNotNull(repository);
}
}
When running the test from VSCODE I get this error:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type
'com.scope.mdm.domain.LegalEntityReadOnlyRepository' available:
expected single matching bean but found 2:
legalEntityRepository,legalEntityReadOnlyRepository
I tried with the #Qualifier annotation to explicitly specify the bean but the result is the same.
Basically, I have created all of the pojo and layers(including the repository layer) necessary for Spring Boot to automatically implement MySql commands. When I trying to run the programme, I get the following command:
Description:
Parameter 0 of constructor in com.fsse2207.project_backend.api.ProductApi required a bean of type 'com.fsse2207.project_backend.service.ProductService' that could not be found.
Action:
Consider defining a bean of type 'com.fsse2207.project_backend.service.ProductService' in your configuration.
It turns out there's sth wrong about the bean in my ProductApi. It says "
Could not autowire. No beans of 'ProductService' type found." How do I fix it?
The following is the interface under the service layer:
package com.fsse2207.project_backend.service;
import com.fsse2207.project_backend.data.ProductCreateData;
import com.fsse2207.project_backend.data.ProductDetailData;
import com.fsse2207.project_backend.exception.ProductFoundByIdException;
import org.springframework.stereotype.Service;
public interface ProductService {
ProductDetailData createProductData (ProductCreateData productCreateData) throws ProductFoundByIdException;
}
The following is the service class:
package com.fsse2207.project_backend.service.impl;
import com.fsse2207.project_backend.data.ProductCreateData;
import com.fsse2207.project_backend.data.ProductDetailData;
import com.fsse2207.project_backend.data.entity.ProductEntity;
import com.fsse2207.project_backend.exception.ProductFoundByIdException;
import com.fsse2207.project_backend.repository.ProductRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class ProductServiceImpl {
private ProductRepository productRepository;
#Autowired
public ProductServiceImpl(ProductRepository productRepository){
this.productRepository=productRepository;
}
public ProductDetailData createProductData (ProductCreateData productCreateData) throws ProductFoundByIdException {
ProductEntity productEntity=new ProductEntity(productCreateData);
if(productRepository.existsById(productEntity.getpId())){
throw new ProductFoundByIdException();
}
return new ProductDetailData(productRepository.save(productEntity));
}
}
The following is the Api:
package com.fsse2207.project_backend.api;
import com.fsse2207.project_backend.data.ProductCreateData;
import com.fsse2207.project_backend.data.ProductDetailData;
import com.fsse2207.project_backend.data.dto.CreateRequestDto;
import com.fsse2207.project_backend.data.dto.CreateResponseDto;
import com.fsse2207.project_backend.exception.ProductFoundByIdException;
import com.fsse2207.project_backend.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ProductApi {
private ProductService productService;
#Autowired
public ProductApi(ProductService productService){
this.productService=productService;
}
#PostMapping
public CreateResponseDto createResponseDto(#RequestBody CreateRequestDto createRequestDto) throws ProductFoundByIdException {
ProductCreateData productCreateData=new ProductCreateData(createRequestDto);
ProductDetailData productDetailData =productService.createProductData(productCreateData);
return new CreateResponseDto(productDetailData);
}
}
I found the problem:
I didn't add the implements keyword in the class definition of ProductServiceImpl so it was not connected to the bean, aka the interface, aka the service layer.
First of all you should not add annotation #Service for ProductService interface.
Moreover this can happen when you have your Class Application in "another package".
You can solve the problem using annotation #ComponentScan (basePackages = {"your.company.domain.package"})
I have a repository, annotated with #Repository
package com.jeppa.interfaces;
import com.jeppa.entities.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends CrudRepository<User, String> {
User findByUserEmailIgnoreCase(String useremail);
}
Part of my controller:
package com.jeppa.controllers;
import com.jeppa.entities.ConfirmationToken;
import com.jeppa.entities.User;
import com.jeppa.interfaces.TokenRepository;
import com.jeppa.interfaces.UserRepository;
import com.jeppa.mail.EmailSenderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class UserAccountController {
#Autowired
private UserRepository userRepository;
#Autowired
private TokenRepository tokenRepository;
#Autowired
private EmailSenderService emailSenderService;
#RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView displayRegistration(ModelAndView modelAndView, User user){
modelAndView.addObject("user", user);
modelAndView.setViewName("register");
return modelAndView;
}
//////////////
And, finnaly, my #SpringBootApplication:
package com.jeppa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class RunApplication {
public static void main(String[] args) {
SpringApplication.run(RunApplication.class, args);
}
}
and i keep getting this error
*************************** APPLICATION FAILED TO START
Description:
Field userRepository in com.jeppa.controllers.UserAccountController
required a bean of type 'com.jeppa.interfaces.UserRepository' 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.jeppa.interfaces.UserRepository'
in your configuration.
what am i doing wrong? here's my project structure:
structure
With Spring Boot, you should generally rely on Spring Boot starters to autoconfigure your dependencies. In case of Spring Data add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
into your pom file to autoconfigure Spring Data instead of directly relying on Spring Data (spring-data-jpa), which requires further manual configuration.
Also don't forget to add and configure an actual implementation as well (h2, jdbc, etc.)
Hi All
I’m trying to execute a Junit test in a Spring boot application, the Junit should test some CRUD operations, I’m using Spring Repositories specifically JpaRepository.
The Repository calss:
package au.com.bla.bla.bla.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import au.com.bla.bla.bla.entity.Todo;
public interface TodoRepository extends JpaRepository<Todo, Integer> {
}
TodoController class
package au.com.bla.bla.bla.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import au.com.bla.bla.bla.entity.Todo;
import au.com.bla.bla.bla.repository.TodoRepository;
import java.util.List;
import java.util.Map;
#RestController
#CrossOrigin
public class TodoController
{
static final String TEXT = "text";
#Autowired
private TodoRepository todoRepository;
...
#RequestMapping(path = "/todo", method = RequestMethod.POST)
public Todo create(#RequestBody Map<String, Object> body)
{
Todo todo = new Todo();
todo.setCompleted(Boolean.FALSE);
todo.setText(body.get(TEXT).toString());
todoRepository.save(todo);
return todo;
}
...
The JUnit:
package au.com.bla.bla.bla.controller;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import au.com.bla.bla.bla.repository.TodoRepository;
#WebMvcTest(TodoController.class)
#RunWith(SpringRunner.class)
public class TodoControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private TodoController subject;
#Before
public void setUp() {
}
#Test
public void testCreate() throws Exception {
String json = "{\"text\":\"a new todo\"}";
mvc.perform(post("/todo").content(json)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.text").value("a new todo"))
.andExpect(jsonPath("$.completed").value(false));
assertThat(subject.getTodos()).hasSize(4);
}
...
Problem:
When executing the Junit I end up with this exception:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'todoController': Unsatisfied dependency expressed through field 'todoRepository': No qualifying bean of type [au.com.bla.bla.bla.repository.TodoRepository] found for dependency [au.com.bla.bla.bla.repository.TodoRepository]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [au.com.bla.bla.bla.repository.TodoRepository] found for dependency [au.com.bla.bla.bla.repository.TodoRepository]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:569) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
…
Can anyone help with this ?
Thanks in advance
Your error is actually the expected behavior of #WebMvcTest.
You basically have 2 options to perform tests on your controller.
1. #WebMvcTest - need to use #MockBean
With #WebMvcTest, a minimal spring context is loaded, just enough to test the web controllers. This means that your Repository isn't available for injection:
Spring documentation:
#WebMvcTest will auto-configure the Spring MVC infrastructure and
limit scanned beans to #Controller, #ControllerAdvice, #JsonComponent,
Filter, WebMvcConfigurer and HandlerMethodArgumentResolver.
Assuming the goal is just to test the Controller, you should inject your repository as a mock using #MockBean.
You could have something like:
#RunWith(SpringRunner.class)
#WebMvcTest(TodoController.class)
public class TodoControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private TodoController subject;
#MockBean
private TodoRepository todoRepository;
#Test
public void testCreate() throws Exception {
String json = "{\"text\":\"a new todo\"}";
mvc.perform(post("/todo").content(json)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.text").value("a new todo"))
.andExpect(jsonPath("$.completed").value(false));
Mockito.verify(todoRepository, Mockito.times(1)).save(any(Todo.class));
}
}
2. #SpringBootTest - you can #Autowire beans
If you want to load the whole application context, then use #SpringBootTest: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
You'd have something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class TodoControllerTest {
private MockMvc mvc;
#Autowired
TodoController subject;
#Autowired
private WebApplicationContext context;
#Before
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void testNoErrorSanityCheck() throws Exception {
String json = "{\"text\":\"a new todo\"}";
mvc.perform(post("/todo").content(json)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.text").value("a new todo"))
.andExpect(jsonPath("$.completed").value(false));
assertThat(subject.getTodos()).hasSize(4);
}
}
I am working on Spring and hibernate project. For database communication we have conventional two layered implementation (i.e DAO layer and Service layer). I have following files:
DemoDao.java
package net.dao;
import java.util.List;
import net.domain.Demo;
public interface DemoDao
{
public List<Demo> get();
}
DemoDaoImpl.java
package net.dao;
import java.util.List;
import net.domain.Demo;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
#Repository
public class DemoDaoImpl implements DemoDao
{
#Autowired
SessionFactory sessionFactory;
public List<Demo> get()
{
Session session=sessionFactory.openSession();
List<Demo> list=session.createQuery("from Demo").list();
session.close();
return list;
}
}
That was DAO layer
Follwing is from service layer:
DemoManager.java
package net.service;
import java.util.List;
import net.domain.Demo;
public interface DemoManager
{
public List<Demo> get();
}
DemoManagerImpl.java
package net.service;
import java.util.List;
import net.dao.DemoDao;
import net.domain.Demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class DemoManagerImpl implements DemoManager
{
#Autowired
DemoDao demoDao;
public List<Demo> get()
{
List<Demo> list=demoDao.get();
return list;
}
}
Follwing is my controller
FromDualLayerView.java
package net.spring;
import java.util.List;
import net.domain.Demo;
import net.service.DemoManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class FromDualLayerView
{
#Autowired
DemoManager demoManager;
#RequestMapping("/dualLayer")
public ModelAndView toResult(ModelMap map)
{
List<Demo> list=demoManager.get();
map.addAttribute("listData", list);
return new ModelAndView("result");
}
}
My Question
Actually everything works fine, but my question over here is that i am not annotating the DemoDao and DemoManager interface, but i am autowiring them. According to the autowiring definition the entities which are annotated are injected.
The how come the dependency is injected by Spring container?
And how does it work like an Impl class?
Thanks in advance.
DemoManagerImpl is annotated as a service and is the only qualifying bean to be injected in the FromDualLayerView class, as it's the only component which is instance of DemoManager. I suppose you have the component scan turned on as well.
The #Repository annotation and the #Service annotation mean you are annotating them. It's actually a spring best practice to annotate the implementations and not the interfaces.
Your spring config file is scanning the classpath, thus those beans are detected. Turn spring logging up to TRACE and you'll probably see output along the lines of:
"scanning classpath, found target DemoDaoImpl"