NullPointerException: Junit Spring annotation configuration - java

I am learning Spring framework and using this reference,
I have a UkranianSongs class
import javax.annotation.Nonnull;
public class UkrainianSongs implements CompactDisk{
#Override
#Nonnull
public String getTitle(){
return "Ukranian Songs";
}
#Override
#Nonnull
public String getArtist(){
return "Skriabin";
}
}
I am creating a bean in CDPlayerConfig class
#Configuration
#ComponentScan
public class CDPlayerConfig {
#Bean
public CompactDisk anUkranianDisk(){
return new UkrainianSongs();
}
}
And I am autowiring compact disk class in another class
#Component
public class CompactDiskBox {
#Autowired
public CompactDisk anUkrainianDisk;
}
I wrote a JUnit test where I am trying to assert anUkranianDisk bean was initialized correctly and is not null
#RunWith(JUnit4.class)
#ContextConfiguration(classes = CDPlayerConfig.class)
public class CompactDiskBoxTest {
#Autowired(required = true)
public CompactDiskBox compactDiskBox;
#Test
public void testUkranianDisk(){
assertNotNull(compactDiskBox.anUkrainianDisk);
}
}
This throws a NullPointerException as compactDiskBox is null which means it couldn't initialize the bean right. Am I missing something here?

In order to be able to use dependency injection in your unit/integration tests, you have to set up the Spring TestContext Framework. In order to do this you should run your tests with SpringJUnit4ClassRunner.class or SpringRunner.class. SpringRunner.class is just an alias for SpringJUnit4ClassRunner.class.

Related

How to add a bean in SpringBootTest

The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.
Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test
Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})
What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.

Overriding spring #Configuration in an integration test

I have a spring boot configuration class like this:
#Configuration
public class ClockConfiguration {
#Bean
public Clock getSystemClock() {
return Clock.systemUTC();
}
}
and I have some integration tests like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
}
and tests like this:
public class MiscTests extends AbstractIntegrationTest{
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
}
I want to be able to offset the clock bean to run some tests at different times on the day. How do I do this?
NOTE: I see several stack overflow answers similar to this, but I can't get them to work.
Based on other responses, it appears the solution should be something like:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#Configuration
class MyTestConfiguration {
#Bean
public Clock getSystemClock() {
Clock realClock = Clock.systemDefaultZone();
return Clock.offset(realClock, Duration.ofHours(9));
}
}
}
But nothing happens there. Do I need to #Import something? do I need to #Autowired something?
Thanks!
As you are using Spring Boot you can take advantage of the #MockBean annotation:
#SpringBootTest(classes = MyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class AbstractIntegrationTest {
#MockBean
private Clock clockMock;
}
Then you can stub public methods of that bean an each of the tests accordingly and uniquely:
#Test
public void CreateSomethingThatOnlyWorksInThe Morning_ExpectCorrectResponse() {
when(clockMock.getTime()).thenReturn(..);
}
As per javadoc of #MockBean:
Any existing single bean of the same type defined in the context will
be replaced by the mock.
it is the #TestConfiguration annotation that you need https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/TestConfiguration.html
#RunWith(SpringRunner.class)
public class ClockServiceImplIntegrationTest {
#TestConfiguration
static class TestOverridingClockServiceConfiguration {
#Bean
public ClockService clockService() {
return new ClockServiceImpl();
}
}
#Autowired
private ClockService clockService;
#MockBean
private ClockRepository clockRepository;
// write test cases here
}
In the case you have existing configuration you c

MockBean doesn't work in Spring boot integration test

I have Spring Integration test where I'm trying to Mock some of my Beans. For some reason although I Mocked them they are NULL. Here is code snippet:
The Bean which I want to Mock
#Component
public class MockWS {
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
The class where the Bean is used
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private MockWS mock;
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Spring boot integration test where the Bean has been mocked
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean(name="mockWS")
MockWS mockService;
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}
Output from the build execution: TEST BEAN: null
In my case the following combination of annotations worked:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { ControllerThatIAmTesting.class })
#AutoConfigureMockMvc(addFilters = false) // if using MockMvc object
But I had to declare explicitly both Autowired objects that I use in the ControllerThatIAmTesting in the test class with #MockBean annotation - otherwise Spring would complain that it cannot find suitable implementation - incidentally both my interfaces and their implementations are in the same corresponding packages
Also, using #WebMvcTest instead of #SpringBootTest (other suggest it as more specific scenario) resulted in Spring failing to find and initialize some other #Autowired dependencies from my #Configuration classes.
Related posts post1 post2 post3
You should mock an interface, not a class. Also, SmDpES2PortImpl must be a Spring bean. Try the following:
Interface:
public interface IMockWS {
public String callSoapClient() throws JAXBException;
}
Component class:
#Component
public class MockWS implements IMockWS {
#Override
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
Service class:
#Service //Also #Component is a good alternative
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private IMockWS mock; //Notice that you are wiring an interface
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean
IMockWS mockService; //Again, you are mocking the interface, not the implementing class
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}

Testing using spring boot throws null pointer exception

I am new to spring boot and i am trying to test a very simple class. But when i run the testMe() below I get exception below
java.lang.NullPointerException
at MyTest.testMe(MyTest.java:25)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
My understanding is when the context is loaded all the beans are initialized and object HelloWorld is created and are autowired in MyTest call. But helloWorld object is null at line helloWorld.printHelloWorld();
I need assistance here to understand what is missing.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(classes = {AppConfigTest.class})
public class MyTest {
#Mock
#Autowired
private Message myMessage;
#Autowired
private HelloWorld helloWorld;
#Test
public void testMe(){
helloWorld.printHelloWorld();
}
}
#Configuration
public class AppConfigTest {
#Bean
public HelloWorld helloWorld() {
return new HelloWorldImpl();
}
#Bean
public Message getMessage(){
return new Message("Hello");
}
}
public interface HelloWorld {
void printHelloWorld();
}
public class HelloWorldImpl implements HelloWorld {
#Autowired
Message myMessage;
#Override
public void printHelloWorld() {
System.out.println("Hello : " + myMessage.msg);
}
}
public class Message {
String msg;
Message(String message){
this.msg = message;
}
}
You're running your tests with a runner that's not Spring-aware, so no wiring is happening. Look at the Spring Boot testing documentation, all their examples use #RunWith(SpringRunner.class). To mock a bean, annotate it with #MockBean, not #Mock. Make sure that the spring-boot-starter-test is included in your POM.

SpringBoot application doesn't autowire field

I do have ServiceImpl which looks like this:
#Service
#RequiredArgsConstructor
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
And I would like to inject a field value to fieldA in an Application.java from application.yml like this:
#EnableSwagger2
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
But I receive the following error when running SpringBoot app:
Error creating bean with name 'serviceAImpl' defined in URLNo qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Do you have any solution for that?
You annotated your class with #Service and defined it manually as a bean with the #Bean annotation. I do think the second is the way you planned to use it.
The #Service annotation will make this class get picked up by Spring's component scan and additionally create an instance of it.
Of course it tries to resolve the parameters and fails when it tries to find a matching "bean" for the String field because there is no simple String bean (and should not :) ).
Remove the #Service annotation and everything should work as expected.
Try this
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowire
public ServiceAImpl(#Value("${fieldA}") String fieldA){
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
and this
#EnableSwagger2
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You should not use #Service and #Bean for the same class!
Spring is not so smart :)
You should annotate your bean like:
#RequiredArgsConstructor
public class ServiceAImpl {
#Value("${fieldA}")
private final String something;
...
But I'm not sure it will work with the #RequiredFieldsConstructor, it would be simpler for you write down the constructor annotated with #Autowired and using the #Value annotation for the String parameter:
#Autowired
public ServiceAImpl(#Value("${aProp}") String string) {
You're using two bean declaration mechanisms:
You're registering your bean using #Service
You're registering a bean using #Bean
This means that your service will be created twice. The one defined using #Bean works properly, since it uses the #Value annotation to inject the proper value in your service.
However, the service created due to #Service doesn't know about the #Value annotation and will try to find any bean of type String, which it can't find, and thus it will throw the exception you're seeing.
Now, the solution is to pick either one of these. If you want to keep the #Bean configuration, you should remove the #Service annotation from ServiceAImpl and that will do the trick.
Alternatively, if you want to keep the #Service annotation, you should remove the #Bean declaration, and you should write your own constructor rather than relying on Lombok because this allows you to use the #Value annotation within the constructor:
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
/**
* This constructor works as well
*/
public ServiceAImpl(#Value("${fieldA}") String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
If you want to declare ServiceAImpl as a Spring bean in your Java Configuration file, you should remove the #Service annotation from the class declaration. These annotations doesn't work well together.
ServiceAImpl.java
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowired
public ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
Application.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
}
Your application.properties
fieldA=value
The below implementation works well for me. You have two issues, first you have to choose between #Service and #Bean and the other issue I've seen in your code was the #Value annotation, you have to use only to inject a value from the properties.
#SpringBootApplication
public class TestedValueApplication {
#Autowired
void printServiceInstance(ServiceA service) {
System.out.println("Service instance: " + service);
System.out.println("value==value? " + service.isFieldA("value"));
}
public static void main(String[] args) {
SpringApplication.run(TestedValueApplication.class, args);
}
#Bean
public ServiceA serviceA(#Value("${fieldA}") String fieldA) {
return new ServiceAImpl(fieldA);
}
}
Service:
public class ServiceAImpl implements ServiceA {
private String fieldA;
ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
application.properties:
fieldA=value

Categories

Resources