Can not execute controller test with #SpringBootTest - spring

I have a Spring Boot application. Version is 2.3.1.
Main Application looks like:
#AllArgsConstructor
#SpringBootApplication
public class LocalServiceApplication implements CommandLineRunner {
private final DataService dataService;
private final QrReaderServer qrReaderServer;
private final MonitoringService monitoringService;
#Override
public void run(String... args) {
dataService.fetchData();
monitoringService.launchMonitoring();
qrReaderServer.launchServer();
}
public static void main(String[] args) {
SpringApplication.run(LocalServiceApplication.class, args);
}
}
After the application is started I have to execute 3 distinct steps which have done with CommandLineRunner:
first gets remote data and store it locally (for test profile this step is skipped)
start async folder monitoring for file uploads with WatchService.
launch TCP server
I have a controller like:
#Slf4j
#RestController
#AllArgsConstructor
#RequestMapping("/v1/permissions")
public class CarParkController {
private final PermissionService permissionService;
#PostMapping
public CarParkPermission createPermission(#RequestBody #Valid CarParkPermission permission) {
return permissionService.createPermission(permission);
}
}
Ant test with Junit 5 looks like:
#ActiveProfiles("test")
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PermissionService permissionService;
private final Gson gson = new Gson();
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(56)
.permissionCode("1234")
.build();
when(permissionService.createPermission(refEq(permission))).thenReturn(permission);
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(instance)));
}
}
Looks like it should work fine.
However, the test isn't executed:
2020-08-27 14:42:30.308 INFO 21800 --- [ main] c.s.i.CarParkControllerIntegrationTest : Started CarParkControllerIntegrationTest in 8.593 seconds (JVM running for 10.03)
2020-08-27 14:42:30.334 INFO 21800 --- [ main] c.s.s.s.DataServiceTestImpl : Fetch data for test profile is skipped
2020-08-27 14:42:30.336 DEBUG 21800 --- [ carpark-ex-1] c.s.monitoring.MonitoringServiceImpl : START_MONITORING Results from Cameras for folder: D:\results-from-camera
2020-08-27 14:42:30.751 DEBUG 21800 --- [ main] c.s.netty.TCPServer : TCP Server is STARTED : port 9090
After those lines execution hangs up forever.
UPDATE
Here are details for monitoring task:
#Async
#Override
public void launchMonitoring() {
log.debug("START_MONITORING Results from Cameras for folder: {}", properties.getFolder());
try {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == ENTRY_CREATE) {
log.info("FILE_CREATED: {}", event.context());
// processing resource
deleteResource(zipFullPath);
} else if (kind == ENTRY_DELETE) {
log.info("RESOURCE_DELETED: {}", event.context());
}
}
key.reset();
}
} catch (InterruptedException e) {
log.error("interrupted exception for monitoring service", e);
Thread.currentThread().interrupt();
}
}
Also AsyncConfiguration is configured with TaskExecutor.
Launch method from TCPServer looks:
#Override
public void launchServer() {
try {
ChannelFuture serverChannelFuture = serverBootstrap.bind(hostAddress).sync();
log.debug("TCP Server is STARTED : port {}", hostAddress.getPort());
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
shutdownQuietly();
}
}
How to solve this issue?

Have understood that execution is blocked (thanks to M.Deinum).
So changed the last method for:
#Async
#Override
public void launchServer() {
// ...
}
And shifted to ObjectMapper instead of Gson for converting instance to JSON format:
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper mapper;
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(444)
.permissionCode("1234")
.build();
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(instance)));
}
}
And finally, it works fine.

Related

Why container does not start automatically in Test?

I have the following class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#RunWith(SpringRunner.class)
#Testcontainers
#ContextConfiguration(initializers = OperationCodeRepositoryTest.DockerDatasourceInitializer.class)
public class OperationCodeRepositoryTest {
#Container
private static MSSQLServerContainer<?> container = new MSSQLServerContainer<>(
"mcr.microsoft.com/mssql/server:2019-latest").acceptLicense()
.withInitScript("database/init.sql");
#Autowired
private OperationCodeRepository repository;
public static class DockerDatasourceInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext, "spring.datasource.url=" + container.getJdbcUrl(),
"spring.datasource.username=" + container.getUsername(),
"spring.datasource.password=" + container.getPassword());
}
}
}
// SOme tests hre
Whenever I try to run my tests,I get the following error :
java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
I thought that having the #Container would be enough to start the container.
The only option for me is to manually start the container by calling the start method in #BeforeClass
Ex :
#BeforeClass
public static void init() {
try{
container.start();
} catch(Exception e){
e.printStackTrace();
}
}
What am I doing wrong ?
THanks

Spring State Machine does not maintain order in which actions are triggered when configuration is read from DB

StateMachineFactory is configured to read configuration from db repository. However then statemachine is created, and events are send to machine, the order of actions executed is not maintained. I suspect, it's because the results returned from DB are not in any particular order.
Following is a POC for the same. Logs are also attached
#Configuration
#EnableStateMachineFactory
public class Config extends StateMachineConfigurerAdapter<String, String> {
#Autowired
private StateRepository<? extends RepositoryState> stateRepository;
#Autowired
private TransitionRepository<? extends RepositoryTransition> transitionRepository;
#Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
#Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
}
#Bean
public Action<String, String> action1() {
return context -> System.out.println("Action1");
}
#Bean
public Action<String, String> action2() {
return context -> System.out.println("Action2");
}
}
#SpringBootApplication
public class Main implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
#Autowired
StateMachineFactory<String, String> factory;
#Autowired
ApplicationContext context;
#Override
public void run(String... args) throws Exception {
JpaStateRepository stateRepository = context.getBean(JpaStateRepository.class);
JpaTransitionRepository transitionRepository = context.getBean(JpaTransitionRepository.class);
JpaActionRepository actionRepository = context.getBean(JpaActionRepository.class);
JpaRepositoryState stateS1 = new JpaRepositoryState("UNPAID", true);
JpaRepositoryState stateS2 = new JpaRepositoryState("DONE");
stateRepository.save(stateS1);
stateRepository.save(stateS2);
var action1 = new JpaRepositoryAction();
action1.setName("action1");
var action2 = new JpaRepositoryAction();
action2.setName("action2");
actionRepository.save(action1);
actionRepository.save(action2);
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(null, stateS1, stateS1, "TEST", Set.of(action1, action2));
transitionRepository.save(transitionS1ToS2);
for (int i = 0; i < 10; i++) {
var sm = factory.getStateMachine();
sm.startReactively().block();
sm.sendEvent("TEST");
}
}
}
Logs:
2022-10-12 22:01:19.474 INFO 58704 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7082 (http) with context path ''
2022-10-12 22:01:19.482 INFO 58704 --- [ restartedMain] com.poc.Main : Started Main in 2.076 seconds (JVM running for 2.288)
Action2
Action1
Action1
Action2
Is there any way to achieve order when done configuration is read from DB?

How to use Spring boot AutoWired and ScheduledExecutorService?

I need to use autowired in more than one class with ScheduledExecutorService, what I have tried is shown in this code. logging size of User list in below example always shows 0, even after user added to arraylist. How to properly use Autowired and ScheduledExecutorService in spring boot?
#Component
public class AnotherClass {
List<User> users = new ArrayList();
public void addUser(User user){
users.add(user);
}
public void logUsers(){
logger.info("User size " + users.size()); <================= Always logs 0, when called from executor
}
}
#RestController
public class SecondClass {
#Autowired
private AnotherClass anotherClass;
#GetMapping(value="/user/test")
public void logUsers(){
anotherClass.addUser(new User());
}
}
Application Class
#Component
#SpringBootApplication
public class SpringBootDemoApplication {
private ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
#Autowired
private AnotherClass anotherClass;
#PostConstruct
public void init() {
logger();
}
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
public void logger(){
exec.scheduleAtFixedRate(new Runnable(){
#Override
public void run(){
try {
anotherClass.logUsers();
}catch (Exception e){
}
}
}, 2000, 1000, TimeUnit.MILLISECONDS);
}
}
The code works if you use the Spring #Autowired and not the #AutoWired Annotation.

Spring-boot: Beans are null during unit/integration test

I am new to sprint-boot. I have a spring-boot application which is working fine in it's regular path. Now as I am trying to write unit/integration tests, I find that my beans are null.
I appreciate any help on understanding why are they null and how to fix it. It seems that it is not able to pick up properties from the yml at all.Please let me know if any more clarification is required.
To clarify the structure:
The main class:
#SpringBootApplication
#EnableConfigurationProperties(ApplicationConfiguration.class)
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
}
The properties file (src/main/java/resources/application.yml)
http:
url:
protocol: http
baseUrl: ${CONNECTOR_BASE_URL}
connectorListUrl : connectors
The configuration class that is using the above properties (ApplicationConfiguration.java) is :
#ConfigurationProperties(prefix = "http.url")
#Validated
#Data
public class ApplicationConfiguration {
private String protocol;
private String baseUrl;
private String connectorListUrl;
}
Now, the simplified version of the class(ContinuousMonitorServiceTask.java that I am trying to write my test on, looks like :
#Component
#Slf4j
public class ContinuousMonitorServiceTask extends TimerTask {
#Autowired MonitorHttpClient httpClient;
#Autowired ApplicationConfiguration config;
#PostConstruct
public void setUp() {
connectorListUrl =
config.getProtocol() + "://" + config.getBaseUrl() + "/" + config.getConnectorListUrl();
connectorListHeaderParams.clear();
connectorListHeaderParams.put("Accept", "application/json");
connectorListHeaderParams.put("Content-Type", "application/json");
connectorListGetRequest = new HttpGet(connectorListUrl);
httpClient.setHeader(connectorListGetRequest, connectorListHeaderParams);
}
public void fetchList() {
try {
response = httpClient.callApi("Get Connector List", connectorListGetRequest);
log.info(response.toString());
connectorListResponseHandler(response);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
The above code is working fine when I am executing.
Now when I am writing test, I need to mock api calls and hence, I have used MOCK-SERVER and my testSimple1 test has passed which is a simple test to see if the mock server can start and return expected response. However, while debugging simpleTest2, I am seeing
monitorTask is null
appConfig is null
monitorTask is null
Although, I have src/test/resources/application.yml as:
http:
url:
protocol: http
baseUrl: 127.0.0.1:8080
connectorListUrl : connectors
My guess is that appConfig is not able to pick up the properties from application.yml during test and hence everything is null.However, I am not 100% sure about what is happening in real time.
Here is how my test class looks like (Kind of dirty code, but I am putting it in it's current state to show what I have tried so far):
//#RunWith(MockitoJUnitRunner.class)
//#TestPropertySource(locations="classpath:application.yml")
//#RunWith(SpringJUnit4ClassRunner.class)
//#SpringApplicationConfiguration(ApplicationConfiguration.class)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ApplicationConfiguration.class)
//#EnableConfigurationProperties(ApplicationConfiguration.class)
public class ContinousMonitorTest {
private MockMvc mockMvc;
#Mock private MonitorHttpClient httpClient;
#Mock private ApplicationConfiguration appConfig;
#InjectMocks
//#MockBean
//#Autowired
private ContinuousMonitorServiceTask monitorTask;
TestRestTemplate restTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
private static ClientAndServer mockServer;
#BeforeClass
public static void startServer() {
mockServer = startClientAndServer(8080);
}
#AfterClass
public static void stopServer() {
mockServer.stop();
}
private void createExpectationForInvalidAuth() {
new MockServerClient("127.0.0.1", 8080)
.when(
request()
.withMethod("GET")
.withPath("/validate")
.withHeader("\"Content-type\", \"application/json\""),
//.withBody(exact("{username: 'foo', password: 'bar'}")),
exactly(1))
.respond(
response()
.withStatusCode(401)
.withHeaders(
new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400"))
.withBody("{ message: 'incorrect username and password combination' }")
.withDelay(TimeUnit.SECONDS,1)
);
}
private GenericResponse hitTheServerWithGETRequest() {
String url = "http://127.0.0.1:8080/validate";
HttpClient client = HttpClientBuilder.create().build();
HttpGet post = new HttpGet(url);
post.setHeader("Content-type", "application/json");
GenericResponse response=null;
try {
StringEntity stringEntity = new StringEntity("{username: 'foo', password: 'bar'}");
post.getRequestLine();
// post.setEntity(stringEntity);
response=client.execute(post, new GenericResponseHandler());
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
#Test
public void testSimple1() throws Exception{
createExpectationForInvalidAuth();
GenericResponse response = hitTheServerWithGETRequest();
System.out.println("response customed : " + response.getResponse());
assertEquals(401, response.getStatusCd());
monitorTask.fetchConnectorList();
}
#Test
public void testSimple2() throws Exception{
monitorTask.fetchConnectorList();
}
as #second suggested above, I made a change in the testSimple2 test to look like and that resolved the above mentioned problem.
#Test
public void testSimple2() throws Exception{
mockMvc = MockMvcBuilders.standaloneSetup(monitorTask).build();
Mockito.when(appConfig.getProtocol()).thenReturn("http");
Mockito.when(appConfig.getBaseUrl()).thenReturn("127.0.0.1:8080");
Mockito.when(appConfig.getConnectorListUrl()).thenReturn("validate");
Mockito.when(httpClient.callApi(Mockito.any(), Mockito.any())).thenCallRealMethod();
monitorTask.setUp();
monitorTask.fetchConnectorList();
}
Alternatively, could have done:
#Before
public void init()
{
MockitoAnnotations.initMocks(this);
}

How do I write a unit test to verify async behavior using Spring 4 and annotations?

How do I write a unit test to verify async behavior using Spring 4 and annotations?
Since i'm used to Spring's (old) xml style), it took me some time to figure this out. So I thought I answer my own question to help others.
First the service that exposes an async download method:
#Service
public class DownloadService {
// note: placing this async method in its own dedicated bean was necessary
// to circumvent inner bean calls
#Async
public Future<String> startDownloading(final URL url) throws IOException {
return new AsyncResult<String>(getContentAsString(url));
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Next the test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class DownloadServiceTest {
#Configuration
#EnableAsync
static class Config {
#Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
#Autowired
private DownloadService service;
#Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
Future<String> content = service.startDownloading(url);
assertThat(false, equalTo(content.isDone()));
final String str = content.get();
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
}
}
If you are using the same example in Java 8 you could also use the CompletableFuture class as follows:
#Service
public class DownloadService {
#Async
public CompletableFuture<String> startDownloading(final URL url) throws IOException {
CompletableFuture<Boolean> future = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
getContentAsString(url);
future.complete(true);
return null;
});
return future;
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Now the test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class DownloadServiceTest {
#Configuration
#EnableAsync
static class Config {
#Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
#Autowired
private DownloadService service;
#Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
CompletableFuture<Boolean> content = service.startDownloading(url);
content.thenRun(() -> {
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
});
// wait for completion
content.get(10, TimeUnit.SECONDS);
}
}
Please that when the time-out is not specified, and anything goes wrong the test will go on "forever" until the CI or you shut it down.

Resources