0

I have a config class with below bean creation in that

@Bean
public Subscriber consumeMessage() {
    LOGGER.debug( "Subscriber bean created" );
    //here is some custom logic and as it does not find actual credential file its giving error and failed to create bean at app start.

    return subscriber;
}

Looks like spring is trying to create and load all beans before running unit test and above bean creation is failing due to some business logic.

Note: Its pure Spring based app not spring-boot

    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:145) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_362]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_362]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_362]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_362]
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:104) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:515) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:217) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:144) [testng-6.10.jar:na]
    at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:169) [testng-6.10.jar:na]
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:108) [testng-6.10.jar:na]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_362]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_362]
    at java.lang.Thread.run(Thread.java:750) [na:1.8.0_362]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'consumeMessage' defined in com.teams.docu.config.GcpPubSubConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.cloud.pubsub.v1.Subscriber]: Factory method 'consumeMessage' threw exception; nested exception is BusinessServiceException [details=ExceptionDetails [type=AUTHENTICATION_EXCEPTION, severity=ERROR, errorcode=gcp.credentials.read.error, args=null, validationErrors=null, namedParameters=[]]]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:456) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1288) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863) ~[spring-context-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    ... 18 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.cloud.pubsub.v1.Subscriber]: Factory method 'consumeMessage' threw exception; nested exception is BusinessServiceException [details=ExceptionDetails [type=AUTHENTICATION_EXCEPTION, severity=ERROR, errorcode=gcp.credentials.read.error, args=null, validationErrors=null, namedParameters=[]]]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    ... 36 common frames omitted
Caused by: com.teams.docu.exception.BusinessServiceException: GCP Credential cannot be created from the stream
    at com.teams.docu.service.util.GCPUtil.getGoogleCredentials(GCPUtil.java:89) ~[classes/:na]
    at com.teams.docu.config.GcpPubSubConfiguration.getSubscriber(GcpPubSubConfiguration.java:54) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_362]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_362]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_362]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_362]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    ... 37 common frames omitted
Caused by: java.io.FileNotFoundException: dummyFileLocation (No such file or directory)
    at java.io.FileInputStream.open0(Native Method) ~[na:1.8.0_362]
    at java.io.FileInputStream.open(FileInputStream.java:195) ~[na:1.8.0_362]
    at java.io.FileInputStream.<init>(FileInputStream.java:138) ~[na:1.8.0_362]
    at java.io.FileInputStream.<init>(FileInputStream.java:93) ~[na:1.8.0_362]
    at com.teams.docu.service.util.GCPUtil.getGoogleCredentials(GCPUtil.java:87) ~[classes/:na]
    ... 49 common frames omitted

How to skip/mock the @Configuration class @Bean creation in spring while running unit test.

Updates: I have tried with @ContextConfiguration and below is my test class that I can update

    @ContextConfiguration(classes = GcpPubSubConfig.class)
public class ServiceImplTest
    extends AbstractTestService<TService> {
}
    

Below is framework/common class that we are using while writing unit test that I cant change.

    @ContextConfiguration(locations = {
    "classpath:docu-service.xml"
})
public abstract class AbstractTestService<T>
    extends AbstractTestNGSpringContextTests {

}

but getting following error as the the framework we are using(AbstractTestService) its already using @ContextConfiguration with locations

        java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:145) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_362]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_362]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_362]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_362]
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:104) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:515) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:217) [testng-6.10.jar:na]
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:144) [testng-6.10.jar:na]
    at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:169) [testng-6.10.jar:na]
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:108) [testng-6.10.jar:na]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_362]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_362]
    at java.lang.Thread.run(Thread.java:750) [na:1.8.0_362]
Caused by: java.lang.IllegalStateException: Neither GenericXmlContextLoader nor AnnotationConfigContextLoader supports loading an ApplicationContext from [MergedContextConfiguration@7ea731f8 testClass = ServiceImplTest, locations = '{classpath:docu-service.xml}', classes = '{class com.docu.analytic.service.impl.GcpPubSubConfig}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]: declare either 'locations' or 'classes' but not both.
    at org.springframework.util.Assert.state(Assert.java:94) ~[spring-core-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:233) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) ~[spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    ... 18 common frames omitted
Tests run: 5, Failures: 1, Errors: 0, Skipped: 4, Time elapsed: 2.019 sec <<< FAILURE! - in com.docu.analytic.service.impl.ServiceImplTest
springTestContextPrepareTestInstance(com.docu.analytic.service.impl.ServiceImplTest)  Time elapsed: 1.955 sec  <<< FAILURE!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: Neither GenericXmlContextLoader nor AnnotationConfigContextLoader supports loading an ApplicationContext from [MergedContextConfiguration@7ea731f8 testClass = ServiceImplTest, locations = '{classpath:docu-service.xml}', classes = '{class com.docu.analytic.service.impl.GcpPubSubConfig}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]: declare either 'locations' or 'classes' but not both.

Updates: I have updated my test class as below and I am able to resolve the "declare either 'locations' or 'classes' but not both" error

    @Import(GcpPubSubConfig.class)
public class ServiceImplTest
    extends AbstractTestService<GreenButtonConnectService> {
stackUser
  • 545
  • 3
  • 9
  • 21

3 Answers3

1

If you are using pure spring without boot you can still have two options.

The first one is to use Mockito to mock a problematic bean, for example:

@ExtendWith(MockitoExtension.class)
@ContextConfiguration(classes = {/** your configuration class **/})
class SomeTest {

    @Mock
    private Subscriber subscriber;
    
    // tests are here
}

This is the simplest example. If your bean is injected into another beans that you want to test, this will require additional manipulations of the test, such as using @InjectMocks or more complex things.

The second one is to use a test context configuration to change bean creation code, for example:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {/** your configuration class **/, SomeTest.TestConfiguration.class})
class SomeTest {

    @Configuration
    public static class TestConfiguration {

        @Bean
        public Subscriber consumeMessage() {
            // some dummy test building logic is here
            return subscriber
        }
    }

    @Autowired
    private Subscriber subscriber;

    // tests are here
}

To demonstrate these abilities, I pushed a working example on my github

Maksim Eliseev
  • 487
  • 1
  • 11
0

When you run a unit test Spring tries to initialize a program context completely. But you have several ways to influence what Spring will initialize.

The first way is to use the @MockBean annotation, for example:

@SpringBootTest
class SomeTest {

    @MockBean
    private Subscriber subscriber;
    
    // tests are here
}

But there are a lot of things that you think about, e.g. how the other beans will be interract with mock bean. To solve some problems related to this, Mockito can be used.

The second way is to use @TestConfiguration, for example:

@SpringBootTest
class SomeTest {

    @TestConfiguration
    public static class CustomConfiguration {
    
        @Bean
        public Subscriber consumeMessage() {
            // some dummy test building logic is here
            return subscriber
        }
    }
    
    @Autowired
    private Subscriber subscriber;
    
    // tests are here
}

If you use a test configuration you will be able to do whatever you want to create the Subscriber bean. But this way requires adding additional configuration: spring.main.allow-bean-definition-overriding=true. You can put this line in the application.properties that can be placed in the test reources.

The choice is yours, which of these methods will suit better. And to demonstrate these methods, I've pushed a working example on my github.

Maksim Eliseev
  • 487
  • 1
  • 11
0

One option is to potentially rewrite the unit test such that it only tests a specific class, i.e. without creating an application context. Another suggestion is to add a mocked Subscriber bean to the test application context by using @MockBean annotation, e.g.

@MockBean
Subscriber subscriberMock;

Copied from the @MockBean JavaDoc:

When registered by type, any existing single bean of a matching type (including subclasses) in the context will be replaced by the mock.

matsev
  • 32,104
  • 16
  • 121
  • 156