I need to create an integration test for my app. I used #SpringBootTest(classes = {Application.class}) annotation to boot it, but its launch takes time. So how can i run test, when my app is ready?
The problem is in kafka listener:
#SpringBootApplication
public class Application {
#Autowired
private KafkaConsumeHandler kafkaConsumeHandler;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#KafkaListener(topics = "${kafka.topics.test}", containerFactory = "kafkaListenerContainerFactory")
public void listenRegistred(KafkaMessage consumeKafka) {
kafkaConsumeHandler.handleStartProcess(consumeKafka);
}
If i try to send messages immediately in test, the listener cant catch them. So i used a little pause before sending.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class})
#DirtiesContext
public class ProcessTest {
#ClassRule
public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, "testTopic");
#Test
public void sendTestRegistred() throws Exception {
Thread.sleep(5000); // Need a delay to boot an application
...
}
You need to add the class that is annotated with #SpringBootApplication.
Example:
#SpringBootApplication
public class SpringApp {}
#SpringBootTest(classes = SpringApp.class)
public class IntegrationTest {}
Also, note that the integration test will always be slower than unit test and you need to determinate what kind of test you need to test a certain functionality.
Update after updates in question:
In your case, the delay of the test is caused because of waiting on KafkaEmbded to start. So you have to find a way programmatically to determinate when Kafka is ready. This is one possibility that should work:
#Before
public void setUp() throws Exception {
// wait until the partitions are assigned
for (MessageListenerContainer messageListenerContainer :
kafkaListenerEndpointRegistry.getListenerContainers()) {
ContainerTestUtils.waitForAssignment(messageListenerContainer,
embeddedKafka.getPartitionsPerTopic());
}
Code is taken from here: https://github.com/code-not-found/spring-kafka/blob/master/spring-kafka-avro/src/test/java/com/codenotfound/kafka/SpringKafkaApplicationTest.java#L42
If this doesn't work look for how to wait on KafkaEmbedded start-up. Your problem is not caused by SpringBootTest.
Related
I have Sonarqube complaining we're missing coverage for the application class, even though we already have something like.
#SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles(resolver = TestActiveProfileResolver.class)
class ApplicationTests extends BaseIntegrationTest {
#Autowired
private OAuthController controller;
#Test
void contextLoads() {
assertNotNull(controller);
}
}
Where BaseIntegrationTest start our testContainers.
So we've added:
#Test
public void main() {
Application.main(new String[]{"--spring.port=8081",});
}
Which works for the coverage, but it will fail if the application is already running on our port 8080. Reason why we've adding the "--spring.port=8081" piece as well as
(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT), to see if that would force the app to start under another port perhaps, but that won't do.
The other option I thought of was perhaps having a specific application.yml file under the test folder that points to a different port, but copying everything from our main application.yml file. Not sure if this is the best approach though as that file is quite big and constantly changing.
Thank you.
Not sure about the ports issue, but the way we managed to make the coverage pass was to separate the tests into two classes, as in:
#SpringBootTest
#ActiveProfiles(resolver = TestActiveProfileResolver.class)
class ApplicationTest extends BaseIntegrationTest {
#Autowired
private OAuthController controller;
#Test
void contextLoads() {
assertNotNull(controller);
}
}
And
#ActiveProfiles(resolver = TestActiveProfileResolver.class)
class MainMethodCoverageTest extends BaseIntegrationTest {
#Test
public void main() {
Application.main(new String[]{});
}
}
I want to create a JUnit test for this private method:
#Component
public class ReportingProcessor {
#EventListener
private void collectEnvironmentData(ContextRefreshedEvent event) {
}
}
I tried this:
#ContextConfiguration
#SpringBootTest
public class ReportingTest {
#Autowired
ReportingProcessor reportingProcessor;
#Test
public void reportingTest() throws Exception {
ContextRefreshedEvent contextRefreshedEvent = PowerMockito.mock(ContextRefreshedEvent.class);
Whitebox.invokeMethod(reportingProcessor, "collectEnvironmentData", contextRefreshedEvent);
}
}
When I run the code I get:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
Do you know ho I can fix this issue?
If you don't have any class annotated with #SpringBootApplication and the known main() method, you need to target your component class on the #SpringBootTest annotation.
Usually I do this when I'm building libraries and for some specific scenario I need to have the spring context to unit test them.
Just add this to your code:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ReportingProcessor.class)
public class ReportingTest {
...
Just did it and the test is running.
Edit: Don't know what are you trying to test exactly, just wanted to show how can you fix the error you are getting.
You should use the #RunWith with your #SpringBootTest:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ReportingTest {
#Autowired
ReportingProcessor reportingProcessor;
#Test
public void reportingTest() throws Exception {
ContextRefreshedEvent contextRefreshedEvent = PowerMockito.mock(ContextRefreshedEvent.class);
Whitebox.invokeMethod(reportingProcessor, "collectEnvironmentData", contextRefreshedEvent);
}
}
I have a logic in my application class in a Spring Boot app, but I don't know how to do an unit and an integration test to cover it.
Here is the code.
#SpringBootApplication
public class MlgApplication {
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext run = SpringApplication.run(MlgApplication.class, args);
ListBean ListBean = run.getBean(ListBean.class);
ListBean.createList();
}
}
It's a command line application that runs with 'java -jar mlg.jar'
If you are using Spring initializr, this test will be created for you. You may call it an integration test because it will try to start your application context (thus integrating all classes inisde it). It goes something like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BootApplicationTests {
#Test
public void contextLoads() {
// some more optional integration assertions here
// like asserting number of beans, are they null, etc...
}
}
And for your specific domain logic, you can try to assert if the list is created but I would put that in a separate class as a unit test.
I managed to do it this way, using mockito-core 3.8 and mockito-inline BUT was not able to get Jacoco coverage doing it this way:
#SpringBootTest
#ActiveProfiles("test")
public class AutowireTest {
private static final String ARG = "";
private static final String[] ARGS = new String[]{ARG};
#Autowired
ConfigurableApplicationContext context;
#Test //Junit5
public void main() {
try (MockedStatic<Application> appStatic = Mockito.mockStatic(Application.class);
MockedStatic<SpringApplication> springStatic = Mockito.mockStatic(
SpringApplication.class)) {
appStatic.when(() -> Application.main(ARGS))
.thenCallRealMethod();
springStatic.when(() -> SpringApplication.run(Application.class, ARGS))
.thenReturn(context);
// when
Application.main(ARGS);
//then
appStatic.verify(times(1),
() -> Application.main(ARGS));
springStatic.verify(times(1),
() -> SpringApplication.run(Application.class, ARGS));
}
}
}
So, I am asking why here: How to Unit test Spring-Boot Application main() method to get Jacoco test coverage
We have a Spring based JUnit test class which is utilizing an inner test context configuration class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceTest.Config.class)
public class ServiceTest {
#Test
public void someTest() {
...
#Configuration
#PropertySource(value = { "classpath:application.properties" })
#ComponentScan({ "..." })
public static class Config {
...
New functionalities have been recently introduced to the Service class, for which the concerned tests should be added to ServiceTest. However these would also require a different test context configuration class to be created (the internals of the existing Config class are fairly complex and change it to serve both old and new tests seems to be be extremely difficult if possible at all)
Is there a way to achieve that certain test methods in one test class would use one config class and other methods would use another? #ContextConfiguration seems to be applicable only on class level, so solution could be to create another test class for the new tests which would utilize its own context configuration class; but it would mean that the same Service class is being covered via two different test classes
With Aaron's suggestion of manually building the context I couldn't find any good examples so after spending some time getting it working I thought I'd post a simple version of the code I used in case it helps anyone else:
class MyTest {
#Autowired
private SomeService service;
#Autowired
private ConfigurableApplicationContext applicationContext;
public void init(Class<?> testClass) throws Exception {
TestContextManager testContextManager = new TestContextManager(testClass);
testContextManager.prepareTestInstance(this);
}
#After
public void tearDown() throws Exception {
applicationContext.close();
}
#Test
public void test1() throws Exception {
init(ConfigATest.class);
service.doSomething();
// assert something
}
#Test
public void test2() throws Exception {
init(ConfigBTest.class);
service.doSomething();
// assert something
}
#ContextConfiguration(classes = {
ConfigATest.ConfigA.class
})
static class ConfigATest {
static class ConfigA {
#Bean
public SomeService someService() {
return new SomeService(new A());
}
}
}
#ContextConfiguration(classes = {
ConfigBTest.ConfigB.class
})
static class ConfigBTest {
static class ConfigB {
#Bean
public SomeService someService() {
return new SomeService(new B());
}
}
}
}
I use these approaches when I'm have to solve this:
Manually build the context in a setup method instead of using annotations.
Move the common test code to a base class and extend it. That allows me to run the tests with different spring contexts.
A mix of the two above. The base class then contains methods to build spring contexts from fragments (which the extensions can override). That also allows me to override test cases which don't make sense or do extra pre/post work in some tests.
Keep in mind that annotations only solve generic cases. You'll have to replicate some or all of their work when you leave the common ground.
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.