I had problem with this test where this endpoint would hang (also tried with WebTestClient and it throws timeout, so the problem is not tied to mock mvc) on after return is called, changed some config and now I am getting HttpMessageNotWritableException.
I am almost sure it's something dealing with mocking the InputStreamResource type.
I have created minimal setup to reproduce this error.
Here is the test class:
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.File;
import java.io.FileInputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.msf.sits.health.mail.MailService;
import org.msf.sits.health.share.FileShareService;
import org.msf.sits.health.share.FileDownloadInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@AutoConfigureWebMvc
@ExtendWith(MockitoExtension.class)
public class FileDownloadControllerTest extends TestcontainersInitializer{
@MockBean
private MailService mailService;
@MockBean
private FileShareService fileShareService;
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
private MockMvc mockMvc;
@Autowired
private HttpMessageConverter[] httpMessageConverters;
@Autowired
private FilesDownloadController controller;
@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(httpMessageConverters).build();
}
@Test
@DisplayName("200 - GET /downloadz/{token}")
void downloadzFileTest() throws Exception {
var token = "GbdARtD594xVpQQqOHAg0cOQSvx5LKGD";
File testFile = new File("src/test/resources", "integration/mocked-file.txt");
InputStreamResource testInputStream = new InputStreamResource(new FileInputStream(
testFile));
when(fileShareService.getFileDownloadInfo(anyString(), anyString())).thenReturn(
new FileDownloadInfo("mocked-file.txt", testInputStream.contentLength(),
testInputStream)
);
MvcResult result = null;
try {
result = mockMvc.perform(MockMvcRequestBuilders
.get("/downloadz/{token}", token)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.accept(MediaType.APPLICATION_OCTET_STREAM))
.andDo(print())
.andExpect(status().isOk()).andReturn();
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
}
}
And here is the controller:
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.msf.sits.health.share.FileShareService;
import org.msf.sits.health.share.MedicalFileDownloadInfo;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class FilesDownloadController {
private final FileShareService fileShareService;
@GetMapping("/downloadz/{token}")
public ResponseEntity<InputStreamResource> downloadFile(@PathVariable String token,
HttpServletRequest request) {
MedicalFileDownloadInfo medicalFileDownloadInfo = fileShareService.getMedicalFileDownloadInfo(
token, request.getRemoteAddr());
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + medicalFileDownloadInfo.getFileName());
var response = ResponseEntity.ok()
.headers(headers)
.contentLength(medicalFileDownloadInfo.getContentLength())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(medicalFileDownloadInfo.getContent());
return response;
}
}
POM.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.msf.sits</groupId>
<artifactId>health</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>health</name>
<description>health</description>
<properties>
<java.version>17</java.version>
<spring-cloud-azure.version>5.5.0</spring-cloud-azure.version>
<vaadin.version>24.1.7</vaadin.version>
<tikka.version>2.9.1</tikka.version>
<springdoc-openapi-starter-webmvc-ui.version>2.0.2</springdoc-openapi-starter-webmvc-ui.version>
<spring-boot.testcontainers.version>3.2.0-M3</spring-boot.testcontainers.version>
<testcontainers.version>1.19.3</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-storage-file-share</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>${tikka.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<version>${spring-boot.testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
<!-- test dependencies -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-dependencies</artifactId>
<version>${spring-cloud-azure.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>2.12.0</version>
<configuration>
<schemaVersion>v2</schemaVersion>
<subscriptionId>${env.SUBSCRIPTION_ID}</subscriptionId>
<resourceGroup>${env.RESOURCE_GROUP}</resourceGroup>
<appName>${env.APP_NAME}</appName>
<pricingTier>${env.TIER}</pricingTier>
<region>${env.REGION}</region>
<runtime>
<os>Linux</os>
<javaVersion>Java 17</javaVersion>
<webContainer>Tomcat 10.0</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.war</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<configLocation>sun_checks.xml</configLocation>
</configuration>
<reportSets>
<reportSet>
<reports>
<report>checkstyle</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.21.0</version>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>production</id>
<build>
<plugins>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<id>frontend</id>
<phase>compile</phase>
<goals>
<goal>prepare-frontend</goal>
<goal>build-frontend</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
In order to fix the problem of infinite hanging caused by the stream mock I had to mock the storageFileInputStream and it's read method in a following way:
The piece of code that sets up the mocks:
Method with implementation of
read()mock: