#Autowire repository works for Application yet fails in JUnitTest - spring

I have a weird issue of #Autowire failing as the following:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'EventTargetRepositoryTest': Unsatisfied dependency expressed through field 'eventTargetRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'EventTargetRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Funnily enough, this happens only on my test which uses the exactly same #Configuration.
#Service beans autowire perfectly in the actual application.
#Repository beans autowire perfectly in the actual application.
#Service beans autowire perfectly(??) in the JUnit Test.
#Repository beans fail to autowire in the JUnit Test.
Code is as follows(package structure omitted):
Test(which fails)
import CoreConfig
import EventTarget
import org.junit.Test
import org.junit.runner.RunWith
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringRunner
#RunWith(SpringRunner::class)
#ContextConfiguration(classes = [CoreConfig::class])
class EventTargetRepositoryTest {
private val LOGGER: Logger = LoggerFactory.getLogger(EventTargetRepositoryTest::class.java)
#Autowired //not autowiring for some reason. needs fixing.
private lateinit var eventTargetRepository: EventTargetRepository
#Test
fun selectOne() {
var eventTarget = EventTarget()
eventTarget.id = 1
eventTargetRepository.selectOne(eventTarget)
LOGGER.info(eventTarget.userId)
}
}
Application(which runs fine, along with working repository)
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.runApplication
#SpringBootApplication(scanBasePackages = ["com.omittedParentName"], exclude = [DataSourceAutoConfiguration::class])
class FrontendApplication
fun main(args: Array<String>) {
runApplication<FrontendApplication>()
}
Config(which the Test and Application both use)
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.FilterType
import org.springframework.context.annotation.PropertySource
import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
#ComponentScan(basePackages = ["com.omittedParentName"], useDefaultFilters = false,
includeFilters = [
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Service::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Component::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Repository::class]),
ComponentScan.Filter(type = FilterType.ANNOTATION, value = [Configuration::class])
])
#Configuration
#PropertySource("classpath:properties/core.properties")
class CoreConfig
repository
import EventTarget
import org.apache.ibatis.annotations.Mapper
import org.springframework.stereotype.Repository
#Mapper
#Repository
interface EventTargetRepository {
fun selectOne(eventTarget: EventTarget): EventTarget
}
mapper xml is irrelevant, so it's omitted.
As we can clearly see, the Test and Application literally use the same #Configuration. Aside from the fact that making all tests require applicationContext is bad structure, the test should work. Other beans like #Service work in the same tests, too.
Any help would be appreciated.
Please refrain from marking as duplicate unless it's a question of #Autowire failing only exclusively on #Repository for only on #Test yet working in release.

Related

Spring boot #RestController test code error

I created a test code for #RestController on the spring boot and this error occurs.
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
---------------------------------------------------------------------------
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberController' defined in file [C:\dev\react\Kculter\target\classes\com\prac\react\controller\MemberController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.prac.react.service.MemberService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
--------------------------------------------------------------------------------------------------
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.prac.react.service.MemberService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I see this problem even though I added #Service annotations to the MemberService class and #RestController annotations to the MemberController class.
How can I solve it?
I'll show my Test code, MemberCotroller, MemberService code below
MemberControllerTest.java
package com.prac.react.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.prac.react.model.dto.Member;
import com.prac.react.service.MemberServiceTest;
#WebMvcTest(MemberController.class)
public class MemberControllerTest {
#Autowired
MockMvc mvc; // 가상의 http request를 테스트 할때 만들기 위해서 사용하는 인스턴스
#Autowired
ObjectMapper obm;
Logger logger = LoggerFactory.getLogger(MemberServiceTest.class);
#Test
#DisplayName("로그인 테스트 1 ") // 회원이 존재할때를 가장했을때를 위한 테스트 코드
void testSignInMember() throws Exception {
// given
Member mb = new Member(1, "hankgood95#gmail.com", "이욱재", true);
String requestBody = obm.writeValueAsString(mb);
mvc.perform(post("/member")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) //status가 200이고
.andExpect(content().string(".com")) //content안에 .com이 있다면
.andDo(print()); //요청받은것들으 print 해라
}
}
MemberController.java
package com.prac.react.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.prac.react.model.dto.Member;
import com.prac.react.service.MemberService;
/* 이파일은 회원가입,로그인,회원정보수정 등등
회원 정보와 관련된 일을 할때 들어올 Controller 입니다 */
#RestController
public class MemberController {
//로그를 찍어보기 위해서 만든 인스턴스
Logger logger = LoggerFactory.getLogger(MemberController.class);
//MemberService 의존성 주입을 위해 사용할 인스턴스
MemberService ms;
public MemberController(MemberService ms){
this.ms = ms; //의존성 주입
}
#PostMapping("member")
public Member SignInMember(#RequestBody Member member){
if(ms.checkMember(member.getEmail()) > 0){ //이미 우리 회원일때 접근
//이미 우리 회원이라면 여기서 얻은 Member 정보를 가지고 메인페이지로 이동을 해야한다.
member.setCheckMember(true);
return member;
}else{//처음 가입할때 접근
//우리 회원이 아니라면 이제 회원가입 페이지로 이동을 해야한다.
member.setCheckMember(false);
return member;
}
}
}
MemberService.java
package com.prac.react.service;
import org.springframework.stereotype.Service;
import com.prac.react.model.dao.MemberDao;
#Service
public class MemberService {
MemberDao md;
//MemberDao 인스턴스의 의존성 주입을 위해 생성자 안에서 집어 넣어주었습니다.
//여기서 주의해야할점은 의존성 주입이 하나 이상일땐 #Autowired 어노테이션을 꼭 넣어줘야만 합니다.
public MemberService(MemberDao md){
this.md = md;
}
public int checkMember(String email){
return md.checkMember(email);
}
}
When you using the test slice #WebMvcTest:
Regular #Component and #ConfigurationProperties beans are not scanned when the #WebMvcTest annotation is used.
This means your class annotated with #Service is also not configured.
You can use #MockBean to create a mock for this service.
Reference with Example: Spring Boot Reference

Spring injects a bean other than what is specified in #Configuration

Recently I've made an error while wiring beans in Spring that caused a behaviour that I'm unable to replicate. Instead of a property sourced with #Value getting injected into Stuff (see the complete demo code below) a value of another bean of type String defined in #Configuration was used when the application was deployed.
What I find puzzling is that everything works as expected when running locally (including the unit test), the output is foo not kaboom, and that this 'bean swap' happened at all when deployed rather than 'no qualifying bean' error.
The commented out line shows the fix which I think makes the configuration similar to what is in the manual.
What is the problem with my set-up? What would make the code as shown (i.e. without the fix) use kaboom String rather than foo property?
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
#SpringBootApplication
open class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
open class Config {
// ...beans of types other than String in original code...
#Bean
open fun beanBomb(): String {
return "kaboom"
}
#Bean
// fix:
// #Value("\${stuff}")
open fun beanStuff(stuff: String): Stuff {
return Stuff(stuff)
}
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
#Component
class Stuff(#Value("\${stuff}") val stuff: String)
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import javax.annotation.PostConstruct
#Component
class Init {
#Autowired
private lateinit var stuff: Stuff
#PostConstruct
fun init() {
println("stuff: " + stuff.stuff)
}
}
// application.properties
stuff=foo
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#TestPropertySource(properties = {"stuff=testFoo"})
class DemoApplicationTests {
#SpyBean
private Stuff stuff;
#Test
void test() {
assertEquals("testFoo", stuff.getStuff());
}
}
Also, is the #Value annotation in Stuff necessary once the fix has been applied? If I uncomment the fix, remove #Value from Stuff and add the following annotation to the test class the test passes:
#ContextConfiguration(classes = {Config.class})
but when I run the app it prints kaboom...
You can check the order in which the bean is being created. if the bean is created before than in my view the Spring IoC container inject the value by type i.e. kaboom and since the Bean of any type is singleton by default, the instance of Stuff won't come into effect even though it is annotated with #component.
In your test you're loading the configuration manually where the bean of Stuff defined in Config is being injected not the Stuff annotated with #component.
The problem is the annotation needs to go on the parameter not the function.
In your way Spring is looking for a bean that meets the Type of String and there is a bean of Type String produced by the function beanBomb(). If you move the annotation like this it should remove the ambiguity.
#Bean
open fun beanStuff(#Value("\${stuff}") stuff: String): Stuff {
return Stuff(stuff)
}
I would add tho, that it's a bit unusual to have a bean of Type String, but I suppose if you don't want to use property/yaml files it would allow you to change a String based on profile.

How to force exclude Spring #Configuration picked up by recursive #ComponentScan

Our top level #ComponentScan scans a package, which in turn contains #ComponentScan that picks up a #Configuration class we want to ignore. Using excludeFilters on the top level #ComponentScan doesn't seem to let us to exclude the #Configuration we want to avoid.
We are trying to add a dependency to our spring project, and with #ComponentScan, bring in the libraries we want.
However, there is one #Configuration class we want to exclude, as it contains a #Bean using deprecated GuavaCache from spring 4. We are using spring 5, so get a NoClassDefFoundError if try to use the class.
Simple to create our own implementation using the newer spring 5 cache, but can't figure out how to exclude the existing #Configuration. The base package we need to scan is not the one containing the bad class, but instead scans a package which contains other classes with #ComponentScan annotations, which causes it to get picked up.
the annotations we've tried at top level spring-boot Application class, classes in thirdparty.infrastructure.requestcontext are what then #ComponentScan to find JwtCacheConfig we want to exclude
#SpringBootApplication
#ComponentScan(basePackages = {
"com.ourpackage",
"thirdparty.infrastructure.requestcontext"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX,
pattern = "fourthparty\\.common\\.jwtvalidation\\.domain\\.config\\..*"))
public class MyApplication
also tried:
#SpringBootApplication
#ComponentScan(basePackages = {
"com.ourpackage",
"thirdparty.infrastructure.requestcontext"},
excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = fourthparty.common.jwtvalidation.domain.config.JwtCacheConfig.class))
public class MyApplication
class to ignore:
package fourthparty.common.jwtvalidation.domain.config
import com.google.common.cache.CacheBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.Cache
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.guava.GuavaCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit
#Configuration
#EnableCaching
class JwtCacheConfig {
#Value('${px.cache.jwtPublicKeys.maximumSize:50}')
Integer maximumCacheSize
#Value('${px.cache.jwtPublicKeys.expireAfterWrite:1440}')
Integer expirationTime
#Bean
Cache jwtPublicKeyCache(){
return new GuavaCache('jwtPublicKeys', CacheBuilder.newBuilder()
.maximumSize(maximumCacheSize).expireAfterWrite(expirationTime, TimeUnit.MINUTES).build(), true)
}
}
The class we want to put in place
package com.ourpackage.config
import java.time.Duration
import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.Cache
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.caffeine.CaffeineCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
#Configuration
#EnableCaching
class MyJwtCacheConfig
{
#Value('${px.cache.jwtPublicKeys.maximumSize:50}')
Integer maximumCacheSize
// cache it for 24 hours (60 min * 24 hours)
#Value('${px.cache.jwtPublicKeys.expireAfterWrite:1440}')
Integer expirationTime
#Bean
Cache myJwtPublicKeyCache(){
def cacheMap = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMinutes(expirationTime))
.maximumSize(maximumCacheSize)
.build()
return new CaffeineCache('jwtPublicKeys', cacheMap, true)
}
}
Each time trying to start application, that class we want to exclude still gets picked up, and we get:
java.lang.NoClassDefFoundError: org/springframework/cache/guava/GuavaCache
...
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'cacheManager' defined in class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]:
Unsatisfied dependency expressed through method 'cacheManager' parameter 0;
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'jwtPublicKeyCache' defined in class path resource
[fourthparty/common/jwtvalidation/domain/config/JwtCacheConfig.class]:
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cache.Cache]:
Factory method 'jwtPublicKeyCache' threw exception;
nested exception is java.lang.NoClassDefFoundError: org/springframework/cache/guava/GuavaCache

No qualifying bean of type repository when running test but not main application

I'm developing a Spring Boot application following TDD methodology. I've created the main classes (controller, service and repository) this way:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class CrimeServiceImpl implements CrimeService{
#Autowired
private CrimeRepository repository;
...
Controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CrimeController {
#Autowired
private CrimeServiceImpl service = new CrimeServiceImpl();
Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CrimeRepository extends JpaRepository<Crime, Long>{
}
This is the project structure:
If I run the application normally, no error. The classes' methods are empty. Then I've created a test class like this:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = CrimeServiceImpl.class)
#ComponentScan("com.springmiddleware")
#AutoConfigureMockMvc
#SpringBootTest
public class TestCrimeService {
//Calling method getAllCrimes works
#Test
public void returnAllCrimesExists() throws NoSuchMethodException, SecurityException {
List<Crime> list = new ArrayList<>();
assertTrue(this.service.getAllCrimes() == list);
}
And if I run this, the following error is shown and the test fails:
NoSuchBeanDefinitionException: No qualifying bean of type 'com.springmiddleware.repository.CrimeRepository' available: expected at least 1 bean which qualifies as autowire candidate.
I've checked all annotations and it seems to me that all is ok, and I thought if I missed something, even in the normal run the application would fail. What did I got wrong?
I wanted also to make a test class for a JPARepository, and I also encountered the same error message:
NoSuchBeanDefinitionException: No qualifying bean of type
'SomethingRepository' available:
expected at least 1 bean which qualifies as autowire candidate.
I could make it work by adding the 2 following annotations on top of the test class:
#EnableJpaRepositories(basePackageClasses = SomethingRepository.class) // repository
#EntityScan(basePackageClasses = Something.class) // entity of the repository
Now it looks like:
#RunWith(SpringRunner.class)
#EnableJpaRepositories(basePackageClasses = SomethingRepository.class) // repository
#EntityScan(basePackageClasses = Something.class) // entity of the repository
#SpringBootTest(classes = MyDbUnitTestApp.class) // does some #ComponentScan and #EntityScan on the repositories/entities package, and #EnableAutoConfiguration
#ActiveProfiles(Profiles.myTestProfile)
#DatabaseSetup(value = {
"/datasets/dataset1.xml" }, type = DatabaseOperation.CLEAN_INSERT)
public class SomethingRepositoryTest {
#Autowired
private SomethingRepository sut;
#Test
public void findById() {
Something something= sut.findById(1L);
Assert.assertEquals("foobar", something.getName());
}
}

Spring 3.1.1.Release. RequestMappingHandlerMapping Autowired issue

I'm trying to add a WADL method to my Spring Web Service application. I'm following the instructions on a number of pages/posts on the topic here and other help sites on the net.
However, my app keeps failing on the auto wire of the RequestMappingHandlerMapping.
The error is:
Error creating bean with name 'wadlController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping net.my.app.ws.thing.WadlController.handlerMapping; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I've done everything I can think of and search out even added:
mvc:annotation-driven
But the app was entirely annotation drive already, so that didn't seem right.
What else could I be missing? Is it a problem with Spring 3.1.1?
Here is the code for the top section of my controller. I did get it working by auto-wiring the ApplicationContext. However, I don't really like that solution.
package net.stuff.stuff.stuff;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
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.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import net.stuff.wadl.*;
#Controller
#RequestMapping("/application.wadl")
public class WadlController {
private static final Logger log = Logger.getLogger(WadlController.class);
#Autowired
private ApplicationContext ctx;
#RequestMapping(method = RequestMethod.GET, produces = { "application/xml" })
public #ResponseBody WadlApplication generateWadl(HttpServletRequest request) {
RequestMappingHandlerMapping handlerMapping = ctx.getBean(RequestMappingHandlerMapping.class);
WadlApplication result = new WadlApplication();
WadlDoc doc = new WadlDoc();
doc.setTitle("REST Service WADL");
result.getDoc().add(doc);
WadlResources wadResources = new WadlResources();
wadResources.setBase(getBaseUrl(request));
if (handlerMapping == null) {
log.error("handlerMapping is null in WadlController???");
return null;
}

Resources