TongChen
TongChen

Reputation: 1430

how to conditionally not create beans in spring boot?

In my application, I have a component that reads data from other system when the application is started. However, during testing, I don't want this component to be created

@Component
@Slf4j
public class DeviceStatisticsSyncHandler {
    @EventListener
    public void handle(ApplicationReadyEvent event) {
        syncDeviceStatisticsDataSync();
    }

    @Value("${test.mode:false}")
    public  boolean serviceEnabled;
}

I can use condition to solve this, but other code readers need to understand, so I don't think this is a very good method:

@EventListener(condition =  "@deviceStatisticsSyncHandler .isServiceEnabled()")
public void handle(ApplicationReadyEvent event) {
    syncDeviceStatisticsDataSync();
}

public  boolean isServiceEnabled() {
    return !serviceEnabled;
}

@Value("${test.mode:false}")
public  boolean serviceEnabled;

My application doesn't use Profiles, is there any other method to solve this problem.

Spring Boot version:2.1.3

Upvotes: 1

Views: 1906

Answers (3)

Hein Blöd
Hein Blöd

Reputation: 1653

I found a way to achieve this without any further external configuration required.

The idea is to create a general configuration that applies to all integration tests and use @MockBean there to replace the real bean. So one should create a class like this under the test classpath (i.e. that is not scanned during normal application launch):

@Configuration
public class IntegrationTestConfiguration
{
   @MockBean
   public DeviceStatisticsSyncHandler deviceStatisticsSyncHandler;
}

I was actually surprised that @MockBean can be used here, but the Javadoc explicitly points that out: Can be used as a class level annotation or on fields in either @Configuration classes, or test classes that are @RunWith the SpringRunner..

Upvotes: 0

Shailesh Chandra
Shailesh Chandra

Reputation: 2340

for me, it's not the case of the condition rather environment-related. I will solve this problem using spring profile.

Step 1: Create an Interface first

public interface DeviceStatisticsSyncHandler {

    public void handle(ApplicationReadyEvent event);
}

Step 2: Create an Implementation for production

@Component
@Profile("!test")
public class DeviceStatisticsSyncHandlerImpl implements DeviceStatisticsSyncHandler {
            @EventListener
            @Override
            public void handle(ApplicationReadyEvent event) {
                syncDeviceStatisticsDataSync();
            }
        }

step 3: create an implementation of test

@Component
 @Profile("test")
 public class DeviceStatisticsSyncHandlerTestImpl implements DeviceStatisticsSyncHandler {
                @EventListener
                @Override
                public void handle(ApplicationReadyEvent event) {
                    //do Nothing
                }
}

final step

All you need to do is set/toggle the property

-Dspring.profiles.active=test 

or

-Dspring.profiles.active=prod

Upvotes: 2

Mark Bramnik
Mark Bramnik

Reputation: 42441

One possible option is not to load the DeviceStaticsticsSyncHandler at all if you're in a test mode. The "test.mode" is not a good name here, because the production code contains something tightly bound to the tests.

How about the following approach:

@Component
@ConditionalOnProperty(name ="device.stats.handler.enabled", havingValue = "true", matchIfMissing=true) 
public class DeviceStatisticsSyncHandler {
   // do whatever you need here, but there is no need for "test.mode" enabled related code here
}

Now in Tests you can define a test property "device.stats.handler.enabled=false" on the test itself or even place that definition in src/test/reources/application.properties so it will be false for all tests in the module.

An obvious advantage is that this definition is pretty much self explanatory and can be easy understood by other project maintainers.

Upvotes: 2

Related Questions