How to write JUnit test with Spring Autowire? - java

Here are the files that I use:
component.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<context:component-scan
base-package="controllers,services,dao,org.springframework.jndi" />
</beans>
ServiceImpl.java
#org.springframework.stereotype.Service
public class ServiceImpl implements MyService {
#Autowired
private MyDAO myDAO;
public void getData() {...}
}
ServiceImplTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath*:conf/components.xml")
public class ServiceImplTest{
#Test
public void testMyFunction() {...}
}
Error:
16:22:48.753 [main] ERROR o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener#2092dcdb] to prepare test instance [services.ServiceImplTest#9e1be92]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'services.ServiceImplTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private services.ServiceImpl services.ServiceImplTest.publishedServiceImpl; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [services.ServiceImpl] 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)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287) ~[spring-beans.jar:3.1.2.RELEASE]

Make sure you have imported the correct package. If I remeber correctly there are two different packages for Autowiring. Should be :org.springframework.beans.factory.annotation.Autowired;
Also this looks wierd to me :
#ContextConfiguration("classpath*:conf/components.xml")
Here is an example that works fine for me :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "/applicationContext_mock.xml" })
public class OwnerIntegrationTest {
#Autowired
OwnerService ownerService;
#Before
public void setup() {
ownerService.cleanList();
}
#Test
public void testOwners() {
Owner owner = new Owner("Bengt", "Karlsson", "Ankavägen 3");
owner = ownerService.createOwner(owner);
assertEquals("Check firstName : ", "Bengt", owner.getFirstName());
assertTrue("Check that Id exist: ", owner.getId() > 0);
owner.setLastName("Larsson");
ownerService.updateOwner(owner);
owner = ownerService.getOwner(owner.getId());
assertEquals("Name is changed", "Larsson", owner.getLastName());
}

I've done it with two annotations for test class: #RunWith(SpringRunner.class) and #SpringBootTest.
Example:
#RunWith(SpringRunner.class )
#SpringBootTest
public class ProtocolTransactionServiceTest {
#Autowired
private ProtocolTransactionService protocolTransactionService;
}
#SpringBootTest loads the whole context, which was OK in my case.

A JUnit4 test with Autowired and bean mocking (Mockito):
// JUnit starts with spring context
#RunWith(SpringRunner.class)
// spring loads context configuration from AppConfig class
#ContextConfiguration(classes = AppConfig.class)
// overriding some properties with test values if you need
#TestPropertySource(properties = {
"spring.someConfigValue=your-test-value",
})
public class PersonServiceTest {
#MockBean
private PersonRepository repository;
#Autowired
private PersonService personService; // uses PersonRepository
#Test
public void testSomething() {
// using Mockito
when(repository.findByName(any())).thenReturn(Collection.emptyList());
Person person = new Person();
person.setName(null);
// when
boolean found = personService.checkSomething(person);
// then
assertTrue(found, "Something is wrong");
}
}

For Spring 5.x and JUnit 5, writing unit tests is quite different.
We have to use #ExtendWith to register Spring extensions (SpringExtension). This brings Spring into play which activates part of the application context (instantiates and manages beans from selected configuration classes).
Note that this is different from the effect of #SpringBootTest which loads the full application context (and IMHO cannot be considered a unit test).
For example, let's create a configuration class FooConfig which produces a bean named foo1:
#Configuration
public class FooConfig
{
#Bean
public String foo1() { return "Foo1"; }
}
Now, let's write a JUnit 5 test case which injects foo1:
import static org.junit.jupiter.api.Assertions.*;
// ... more imports ...
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {
helloworld.config.FooConfig.class,
})
class FooTests
{
#BeforeAll
static void setUpBeforeClass() throws Exception {}
#AfterAll
static void tearDownAfterClass() throws Exception {}
#BeforeEach
void setUp() throws Exception {}
#AfterEach
void tearDown() throws Exception {}
#Autowired
private String foo1;
#Test
void test()
{
assertNotNull(foo1);
System.err.println(foo1);
}
}

In Spring 2.1.5 at least, the XML file can be conveniently replaced by annotations.
Piggy backing on #Sembrano's answer, I have this. "Look ma, no XML".
It appears I to had list all the classes I need #Autowired in the #ComponentScan
#RunWith(SpringJUnit4ClassRunner.class)
#ComponentScan(
basePackageClasses = {
OwnerService.class
})
#EnableAutoConfiguration
public class OwnerIntegrationTest {
#Autowired
OwnerService ownerService;
#Test
public void testOwnerService() {
Assert.assertNotNull(ownerService);
}
}

I think somewhere in your codebase are you #Autowiring the concrete class ServiceImpl where you should be autowiring it's interface (presumably MyService).

You should make another XML-spring configuration file in your test resource folder or just copy the old one, it looks fine, but if you're trying to start a web context for testing a micro service, just put the following code as your master test class and inherits from that:
#WebAppConfiguration
#RunWith(SpringRunner.class)
#ContextConfiguration(locations = "classpath*:spring-test-config.xml")
public abstract class AbstractRestTest {
#Autowired
private WebApplicationContext wac;
}

You can use #Import annotation. It's more readable and shorter than using the #ComponentScan annotation.
import org.springframework.context.annotation.Import;
#Import(value = {MyService.class, MyInjectedService.class})
public class JUnitTest {
#Autowired
private MyService service;
// ...
}

Related

Spring: Service Class with #Service annotation gets "required bean of type 'abcService' could not be found"

I have a spring batch application in which the writer has an #Autowired field (it is a service class). When running tests for the writer step, I am met with the error:
Field batchTrackingService in com.ally.cr.miscinfo.batch.writer.AdvantageClientItemWriter required a bean of type 'com.test.miscinfo.service.TestService' 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.test.miscinfo.service.batchTrackingService ' in your configuration.
I've looked at a few answers to related questions, and most of them are caused by the fact that the class being injected has not been annotated with #Component, #Service, #Repository, etc. However mine is. I also read questions where the supposed solution was to add the #ComponentScan() annotation to the Main class of my application. After trying this, it gave the same error. Can someone please help me? Any help is appreciated.
Here are the relevant classes:
Main class:
#SpringBootApplication
#EnableBatchProcessing
#EnableJpaRepositories("com.test.miscinfo.repository")
#EntityScan("com.test.miscinfo.entity")
#ComponentScan("com.test.miscinfo.service")
public class MiscInfoServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MiscInfoServiceApplication.class, args);
}
}
Writer class:
#Slf4j
#Component
#AllArgsConstructor
#NoArgsConstructor
public class AdvantageClientItemWriter implements ItemWriter<MiscInfo> {
#Autowired private AdvantageClientConfig advantageClientConfig;
#Autowired WebClient advantageClientWebClient;
#Autowired private BatchTrackingService batchTrackingService;
#Override
public void write(List<? extends MiscInfo> miscInfos) throws Exception {
/* some call to a method in the injected service */
}
}
Service class:
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
#Transactional
#Service
public class BatchTrackingService {
public void someMethod() {}
}
Please let me know if I am missing relevant info.
EDIT:
Adding test method:
#ExtendWith(SpringExtension.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#EnableConfigurationProperties(value = AdvantageClientConfig.class)
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = { AdvantageClientItemWriter.class })
public class AdvantageClientItemWriterTest {
#MockBean RestTemplate advantageClientRestTemplate;
#MockBean WebClient advantageWebClient;
WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
WebClient.RequestBodySpec requestBodySpec = mock(WebClient.RequestBodySpec.class);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
#Autowired AdvantageClientConfig advantageClientConfig;
#Autowired AdvantageClientItemWriter advantageClientItemWriter;
ArgumentCaptor<String> uriCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<MediaType> mediaTypeCaptor= ArgumentCaptor.forClass(MediaType.class);
ArgumentCaptor<String> headerNameCaptor= ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> headerValueCaptor= ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> bodyCaptor= ArgumentCaptor.forClass(String.class);
private MemoryAppender memoryAppender;
#BeforeEach
public void init(){
Logger logger = (Logger) LoggerFactory.getLogger("com.test");
memoryAppender = new MemoryAppender();
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(Level.DEBUG);
logger.addAppender(memoryAppender);
memoryAppender.start();
}
#Test
public void successfulAdvantageClientWrite() throws Exception {
setupMockReturns();
when(responseSpec.toBodilessEntity()).thenReturn(Mono.just(new ResponseEntity(null, HttpStatus.OK)));
List<MiscInfo> miscInfos = new ArrayList<>();
final MiscInfo miscInfo = createMiscInfo1();
miscInfos.add(miscInfo);
advantageClientItemWriter.write(miscInfos);
Assertions.assertEquals(advantageClientConfig.getEndpoint(), uriCaptor.getValue());
Assertions.assertEquals(MediaType.APPLICATION_JSON, mediaTypeCaptor.getValue());
Assertions.assertEquals( advantageClientConfig.getHeaderName(), headerNameCaptor.getValue());
Assertions.assertEquals(advantageClientConfig.getApiKey(), headerValueCaptor.getValue());
Assertions.assertEquals(new ObjectMapper().writer().withDefaultPrettyPrinter().writeValueAsString(miscInfos), bodyCaptor.getValue());
assertThat(memoryAppender.search("Write to Advantage status: ", Level.DEBUG).size()).isEqualTo(1);
}
}
This error means that Spring is trying to autowire of bean of type BatchTrackingService in your AdvantageClientItemWriter but it could not find one in the application context. In other words, your test context does not contain a bean definition of type BatchTrackingService, which could be due to one of the following causes:
Either the configuration class that defines that bean is not imported in the test class (in #ContextConfiguration(classes = { AdvantageClientItemWriter.class })
or the class of that bean is not in the package that is scanned by Spring (Boot).
Make sure that:
the class public class BatchTrackingService {} is defined in the package referenced in #ComponentScan("com.test.miscinfo.service")
the #ContextConfiguration annotation imports the class of that bean (something like #ContextConfiguration(classes = { AdvantageClientItemWriter.class, BatchTrackingService.class }), or that it imports a configuration class which defines an instance of that bean.

Can't Autowire Service in JUnit 4 Test in Spring Boot Multi Module Application

I have following project structure:
-module1
--src/main/java/at.flobau.demo.module1
---model
----Product.java
---service
----ProductService.java
---TestConfiguration.java
--src/test/java/at.flobau.demo.module1.service
---ProductServiceTest.java
-module2
--src/main/java/at.flobau.demo.main
---MainApplication.java
The Application class looks like this:
#SpringBootApplication(scanBasePackages = {"at.flobau.demo.main"})
#PropertySource(value = "classpath:application.properties")
#EnableJpaRepositories("at.flobau.demo.module1")
#EntityScan(basePackages = {"at.flobau.demo.module1"})
public class PocApplication {
public static void main(String[] args) {
SpringApplication.run(PocApplication.class, args);
}
}
The Service looks like this:
#Service
public class ProductService implements IProductService {
#Autowired
private IProductRepository productRepository;
...
}
The test Class looks like this:
#SpringBootTest
#ContextConfiguration(classes = { TestConfiguration.class }, loader =
AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class ProductServiceTest {
#Autowired
private ProductService productService;
...
}
The test configuration file looks like this:
#Configuration
#ComponentScan("at.flobau.demo")
public class TestConfiguration{ }
IntelliJ tells me, that the ProductService inside the test cannot be autowired. When i do run the test i get an exepction:
Error creating bean with name 'ProductServiceTest': Unsatisfied dependency expressed through field
'productService'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type
'at.flobau.demo.module1.products.service.ProductService' available: expected at least 1 bean which
qualifies as autowire candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
You should avoid using field injection (even though is possible) and use constructor injection. This will solve this problem as you will be able to pass the service from the constructor but it will also be useful in the future as you can locate usages and trace objects in your code way better than field injections which are "hidden"
So I recommend instead of trying to solve your problem here to refactor your class in constructor injection and pass the service from there either by directly creating the object in your test or by creating a configuration for your test that will generate the object and give the arguments it requires
something like
#ContextConfiguration(classes = { GeneralTester.TestConfig.class })
#RunWith(SpringRunner.class)
public class GeneralTester {
#TestConfiguration
public static class TestConfig {
#Bean
public IProductService productService(final IProductRepository productRepository){
return new ProductService(productRepository);
}
#Bean
public IProductRepository productRepository(){
return mock(IProductRepository.class);
}
}
#Autowire
public IProductService productService;
#Autowire
public IProductRepository productRepository;
#Before
public void setUp() {
reset(productRepository);
}
#After
public void tearDown() {
verifyNoMoreInteractions(productRepository);
}
#Test
public void doSmth() {
//... your setup
when(productRepository.save(any())).thenReturn("something");
//... your call and assertions
verify(productRepository).save(any());
}
}
You can annotate your test class with #SpringBootTest(classes = ProductService.class)
Have you tried creating a bean of the service in the test configuration class?
#TestConfiguration
#ComponentScan("at.flobau.demo")
public class TestConfiguration {
#Bean
public ProductService productService() {
return new ProductService();
}
}

Getting "At least one JPA metamodel must be present" with #WebMvcTest

I'm fairly new to Spring, trying to do some basic integration tests for a #Controller.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
}
but I'm getting
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Unlike most people posting this error, I don't want to use JPA for this. Am I trying to use #WebMvcTest incorrectly? How can I track down the Spring magic that's inviting JPA to this party?
Remove any #EnableJpaRepositories or #EntityScan from your SpringBootApplication class instead do this:
package com.tdk;
#SpringBootApplication
#Import({ApplicationConfig.class })
public class TdkApplication {
public static void main(String[] args) {
SpringApplication.run(TdkApplication.class, args);
}
}
And put it in a separate config class:
package com.tdk.config;
#Configuration
#EnableJpaRepositories(basePackages = "com.tdk.repositories")
#EntityScan(basePackages = "com.tdk.domain")
#EnableTransactionManagement
public class ApplicationConfig {
}
And here the tests:
#RunWith(SpringRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
}
I had the same problem. #WebMvcTest looks for a class annotated with #SpringBootApplication (in the same directory or higher up in your app structure if it doesn't find one). You can read how this works # https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests.
If your class annotated with #SpringBootApplication also has #EntityScan /#EnableJpaRepositories this error occurs. Because you have these annotations with #SpringBootApplication and you are mocking the service ( so actually not using any JPA ). I found a workaround which may not be the prettiest, but works for me.
Place this class in your test directory ( the root ). #WebMvcTest will find this class before your actual Application class. In this class you don't have to add #EnableJpaRepositories/#EntityScan.
#SpringBootApplication(scanBasePackageClasses = {
xxx.service.PackageMarker.class,
xxx.web.PackageMarker.class
})
public class Application {
}
And your test will look the same.
#RunWith(SpringRunner.class)
#WebMvcTest
#WithMockUser
public class ControllerIT {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
public void testName() throws Exception {
// when(service.xxx(any(xxx.class))).thenReturn(xxx);
// mockMvc.perform(post("/api/xxx")...
// some assertions
}
}
Hope this helps!
Alternatively, you can define a custom configuration class inside your test case, including only the controller (plus its dependencies), to force Spring to use this context.
Please note, you'll still have access to MockMvc and other goodness in your test case, if it's WebMvcTest annotated.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
#Configuration
#ComponentScan(basePackageClasses = { DemoController.class })
public static class TestConf {}
Add #MockBean(JpaMetamodelMappingContext.class) to above of class DemoControllerIntegrationTests:
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
#MockBean(JpaMetamodelMappingContext.class)
public class DemoControllerIntegrationTests {
...
}
Because you have not used a database in your test, Spring throws this exception. By mocking JpaMetamodelMappingContext class you will bypass the needed metamodel.
If anyone uses Spring boot and don't want to remove #EntityScan and #EnableJpaRepositories you can remove #WebMvcTest annotation from your test class and add the following instead:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
//...
}
and you will be able to autowire MockMvc and use it.

How to autowire a Spring bean within a controller when testing?

I have a Spring test :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:my-context.xml")
public class MyTest {
#Test
public void testname() throws Exception {
System.out.println(myController.toString());
}
#Autowired
private MyController myController;
}
This works fine when myController is defined in same class as MyTest but if I move MyController to another class it is not autowired, as running below returns null, so myController does not seem to be autowired correctly :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:my-context.xml")
public class MyTest {
#Test
public void testname() throws Exception {
System.out.println(new TestClass().toString());
}
}
#Controller
public class TestClass {
#Autowired
private MyController myController;
public String toString(){
return myController.toString();
}
}
Does autowiring only occur at within the class the test is being run from ? How can I enable autowiring on all classes that are instantiated by the test class ?
Update :
Thanks to the answers from smajlo & Philipp Sander I was able to fix this using this code to access this bean instead of creating the bean explicitly. This is already configured by Spring so I access it from the context :
ApplicationContext ctx = new ClassPathXmlApplicationContext("my-context.xml");
TestClass myBean = (TestClass) ctx.getBean("testClass");
When the bean is created explicitly it is not autowired by Spring.
new TestClass().toString()
If you creating object by manually invoking constructor obejct is not controlled by Spring so field won't be autowired.
EDIT:
maybe you want to create specific test-context and load both on test classes.. because for now i guess your way to do the testing is a little wrong.
Why do you need from test class access to another test class? This is not unit test anymore:)
Your TestClass will be never autowired no matter what annotation you will add, because you creating new instance..
try this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:my-context.xml")
public class MyTest {
#Autowired
private TestClass testClass;
#Test
public void testname() throws Exception {
System.out.println(testClass.toString());
}
}
#Controller
public class TestClass {
#Autowired
private MyController myController;
public String toString(){
return myController.toString();
}
}
As Sotirios Delimanolis already said:
MyTestClass needs to be managed by Spring to get autowired. to do this, simply at #Component to MyTestClass and autowire it

XML-less configuration for spring

I have the following configuration bean for a non web app
#Configuration
public class MyBeans {
#Bean
#Scope(value="prototype")
MyObject myObject() {
return new MyObjectImpl();
}
}
On the other side I have my class
public class MyCommand implements Command {
#Autowired
private MyObject myObject;
[...]
}
How can I make myCommand be autowired with the configuration in MyBeans without using XML so I can inject mocks in my other test classes?
Thanks a lot in advance.
With XML-based configuration you'd use the ContextConfiguration annotation. However, the ContextConfiguration annotation doesn't appear to work with Java Config. That means that you have to fall back on configuring your application context in the test initialization.
Assuming JUnit4:
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest{
private ApplicationContext applicationContext;
#Before
public void init(){
this.applicationContext =
new AnnotationConfigApplicationContext(MyBeans.class);
//not necessary if MyBeans defines a bean for MyCommand
//necessary if you need MyCommand - must be annotated #Component
this.applicationContext.scan("package.where.mycommand.is.located");
this.applicationContext.refresh();
//get any beans you need for your tests here
//and set them to private fields
}
#Test
public void fooTest(){
assertTrue(true);
}
}

Categories

Resources