Spring MockMvc second test fails when using StreamingResponseBody - spring-boot

I have a simple Spring Boot service that uses StreamingResponseBody to write the response.
When testing this with MockMvc, the first test gives me the correct response, but the second one has an empty response body.
However, when I add a little sleep time to the test, they both work.
Here is my controller (Kotlin)
#RestController
class DummyController() {
#GetMapping()
fun streamIndex() =
ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(StreamingResponseBody { it.write("Hello world".toByteArray()) })
}
And my tests:
#AutoConfigureMockMvc(print = MockMvcPrint.NONE)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DummyControllerTest {
private val controller = DummyController()
private val mvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(GlobalExceptionHandler()).build()
#Test
fun test1() {
mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
}
#Test
fun test2() {
mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
}
}
Thee first test succeeds, the second one fails with:
java.lang.AssertionError: Response content expected:<Hello world> but was:<>
Then when I add a little delay in the test it works:
#Test
fun test2() {
mvc.perform(get("/")).andExpect(status().isOk)
.andDo { Thread.sleep(5) }
.andExpect(content().string("Hello world"))
}
Is this a bug in MockMvc or something missing in my test?
Thanks in advance,
Rob

Instead of adding a delay, you will need to fetch the async result first by doing the following:
#Test
fun test2() {
mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andDo(MvcResult::getAsyncResult)
.andExpect(status().isOk)
.andExpect(content().string("Hello world"))
}
Make sure you add this to both tests.
Source: https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java #line 81

Your test is wrong/weird. You have #SpringBootTest which starts an app on the port, have configured #AutoConfigureMockMvc and totally ignore all of that.
The combination in your #SpringBootTest wouldn't even work as you cannot use RANDOM_PORT with MockMvc (that only works with a MOCK environment). You either use the MOCK environment or switch to TestRestTemplate/TestWebClient for testing.
You probably worked around the issues you had with manually constructing a controller and manually registering MockMvc with that controller.
Finally you are using an async request which you would need to consume properly as well. See async testing in the Spring Reference Guide. It also shows another way of testing using the WebTestClient which is preferred for streaming responses.
In short your test is a bit af a frankenstein test.
I would strongly suggest to use the technologies in a proper way and rewrite your test as follows.
#WebMvcTest(DummyController.class)
class DummyControllerTest {
#Autowired
private val mvc;
#Test
fun test1() {
var mvcResult = mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andExpect(status().isOk)
.andExpect(request().asyncResult("Hello world"))
}
#Test
fun test2() {
mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andExpect(status().isOk)
.andExpect(request().asyncResult("Hello world"))
}
}
This test won't start a full blown container but only the Web MVC stuff and the stuff needed for your controller. In the tests the async result is now also properly parsed and asserted. All in all it should now also run a lot faster due to the reduced things needed to start (unless there wasn't much to start with).

Related

How to stop #CucumberContextConfiguration with #SpringBootTest from reloading application context between every test?

I've got this problem where my application context is reloaded between every test. I'm wiring in my actual application with functional test properties, wiremock etc. to create a functional test environment. Tests have always run fine but now we've added several it's become painfully slow due to the spring application being re-run everytime. The io.cucumber versions I'm using in my pom for cucumber-spring, cucumber-java, cucumber-junit is 7.11.1.
My Functional Test runner is annotated like this:
#RunWith(Cucumber.class)
#CucumberOptions(
features = "classpath:functional/features",
glue = {"com.iggroup.ds.functional.stepdefinitions"},
monochrome = true,
tags = "#FunctionalTest",
plugin = {"pretty", "html:target/cucumber-html-report", "junit:target/cucumber-xml-report.xml"}
)
public class FunctionalTestRunner {
#BeforeClass
public static void beforeClass() {
prepareEnvironment();
}
private static void prepareEnvironment() {
int applicationPort = SocketUtils.findAvailableTcpPort();
System.setProperty("server.port", String.valueOf(applicationPort));
System.setProperty("spring.active.profiles", "FUNCTIONAL_TEST");
System.setProperty("spring.cloud.config.enabled", "false");
System.setProperty("spring.cloud.config.server.bootstrap", "false");
}
}
Inside my glue package the Cucumber Configuration looks like this:
#AutoConfigureWireMock(port = 8089)
#CucumberContextConfiguration
#SpringBootTest(
classes = {
ServiceApplication.class,
RestClients.class
},
webEnvironment = DEFINED_PORT,
properties = {
"spring.profiles.active=FUNCTIONAL_TEST",
"spring.cloud.config.enabled = false"
}
)
public class FunctionalTestSpringCucumberConfiguration {
}
And lastly the application itself looks like this:
#EnableAsync
#EnableCaching
#EnableConfigServer
#SpringBootApplication
#EnableConfigurationProperties
public class ServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
I had read somewhere before that the presence of #MockBean was causing unexpected refreshes between context although I never found out as to why - but I have none defined. As far as I can tell across the articles I've been reading, this shouldn't refresh my context every time so wondering if there's any way I can force it not to rewire the ServiceApplication.class in between every scenario?
#AutoConfigureWireMock(port = 8089)
By using Wiremock on fixed port you are dirtying the application context. This means a new application context will be created for each test. The code responsible for this prints a warning that you can see in your logs.
if (portIsFixed(testContext)) {
if (log.isWarnEnabled()) {
log.warn("You've used fixed ports for WireMock setup - "
+ "will mark context as dirty. Please use random ports, as much "
+ "as possible. Your tests will be faster and more reliable and this "
+ "warning will go away");
}
testContext.markApplicationContextDirty(DirtiesContext.HierarchyMode.EXHAUSTIVE);
}

JUnit tests in Spring Boot 3.0 does not work

Code
GET method inside controller class:
#GetMapping("/getAllRecommendedMovies")
public ResponseEntity<BookMyTicket> getAllRecommendedMovies(
#RequestParam(value = "theatreName", required = false) String theatreName,
#RequestParam(value = "pincode", required = false) Integer pincode,
HttpServletRequest request) {
return Observation.createNotStarted(
request.getRequestURI().substring(1),
observationRegistry
).observe(() -> new ResponseEntity<(
theatreManagementService.getAllRecommendedMovies(theatreName, pincode),
HttpStatus.OK
));
}
JUnit test:
#Test
public void getAllRecommendedMovies() throws Exception {
try (MockedStatic<Observation> utilities = Mockito.mockStatic(Observation.class)) {
utilities.when(
() -> Observation.createNotStarted(Mockito.eq("getAllRecommendedMovies"), Mockito.any())
).thenReturn(Observation.NOOP);
}
mockMvc.perform(get("/getAllRecommendedMovies")).andExpect(status().isOk());
}
Also on Github: TheatreManagementControllerTest.java
Question
I have implemented JUnit test for ObservationRegistry.
Is there any alternate method to implement?
Mockito.mockStatic can only mock static calls that happen in the same thread. See https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks.
Spring MVC tests run the Spring application in its own thread so static mocking with Mockito won't help here.
I suggest you introduce a ObservationService interface to wrap methods like Observation.createNotStarted(..) and use that service in your controller. The service can then be easily mocked using standard Spring testing mechanisms like #MockBean.

Roboelectric with Room Database for android

How to do unit testing of Room Database with the help of Roboeletric?
I don't want to do instrumentation testing.
From what I can tell it can be done like this
//#RunWith(AndroidJUnit4::class)
#RunWith(RobolectricTestRunner::class)
class WordDaoTest {
private lateinit var wordRoomDatabase: WordRoomDatabase
private lateinit var wordDao: WordDao
#get:Rule
var instantTaskExecutor = InstantTaskExecutorRule()
#Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
wordRoomDatabase = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java).allowMainThreadQueries().build()
wordDao = wordRoomDatabase.wordDao()
wordRoomDatabase.wordDao().insertAll(listOf<Word(Word("one"),Word("two"),Word("three"))
}
#After
fun closeDb() {
wordRoomDatabase.close()
}
#Test
fun testGetName() {
Assert.assertThat(getValue(wordDao.getAllLiveWords()).size, equalTo(3))
}
}
It seems though you need allowMainThreadQueries() in the build of the DB.
I am not sure as to why everyone tests the Dao in the instrumentation test when it can be done in the Unit Test and then be added to code coverage (maybe someone else has some insight)
This code is in Kotlin but I am sure it would translate to java just the same.
This was provided to me however as to why it's not considered to be best practice
https://developer.android.com/training/data-storage/room/testing-db
Note: Even though this setup allows your tests to run very quickly, it isn't recommended because the version of SQLite running on your device—and your users' devices—might not match the version on your host machine.
Robolectric can support such JVM unit tests with Room.
To get the required context, please add below dependency in your build.gradle:
testImplementation 'androidx.test:core:1.2.0'
Let's assume that we have a repository class which wraps the Room Dao. And here's a quick example:
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [28]) // This config setting is the key to make things work
class FooRepositoryTest {
#get:Rule
var instantTask = InstantTaskExecutorRule()
private lateinit var database: MyDatabase
private lateinit var sut: FooRepository
#Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context, MyDatabase::class.java)
.allowMainThreadQueries()
.build()
sut = FooRepository(database.fooDao())
}
#After
fun tearDown() {
database.close()
}
}

Test Observable.FlatMap Mockito

I've been looking on internet but haven't found the solution if any (new on UnitTest and Mockito)
It's possible to test a method that return a call of a service and manipulate it's result before to return it? Example;
public Observable<Reports> getUserReports(Integer userId) {
return serviceClient
.getReports(userId)
.flatMap(serviceReports -> {
System.out.println("Testing inside flatMap"); <- never reach this line therefore duno if methods down here are invoked and work perfectly
final Observable<List<Report>> reports = getPendingReports(userId, serviceReports);
//More methods that modify/update information
return zip(observable1, observable2, observable3
(report1, report2, report3) -> {
updateReports(otherArguments, report1, report2, report3);
return serviceReports;
});
});
}
So far I've tried;
#Test
public void myTest(){
when(serviceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
Observable<Reports> result = mocketClass.getUserReports(userId)
}
Tryed with Spy and Mock but no luck so far. Any hint or help would be great.
To mock getReports() behavior you need to mock the serviceClient firstly and pass it into your service class.
Just as example:
#Test
public void myTest(){
// Given
final ServiceClient mockedServiceClient = Mockito.mock(ServiceClient.class);
when(mockedServiceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
// and create an instance of your class under testing with injected mocked service client.
final MyUserService userService = new MyUserService();
userService.setServiceClient(mockedServiceClient);
// When run a method under test
Observable<Reports> actualResult = userService.getUserReports(userId)
// Then
// actualResult to be verified.
}

Using Mockito with TestNG

I took a working test written for JUnit using Mockito and tried to adapt it to work with TestNG but oddly using TestNG only one test will work.
I think it is somehow related to the resetting of the mocks but I have played around with trying to call Mockito.reset and using BeforeMethod and BeforeClass and different combinations but still can only get one test to pass.
What I need to do to get the test to work?
#BeforeClass
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(calculatorController).build();
}
#AfterMethod
public void reset() {
Mockito.reset(calculatorService);
}
#Test
public void addFunctionTest() throws Exception {
Assert.assertNotNull(calculatorController);
Result expectedResult = new Result();
expectedResult.setResult(10);
when(calculatorService.add(anyInt(), anyInt())).thenReturn(expectedResult);
mockMvc.perform(get("/calculator/add").accept(MediaType.APPLICATION_JSON_VALUE)
.param("val1", "100")
.param("val2", "100"))
.andExpect(content().contentType("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", equalTo(10)));
verify(calculatorService, times(1)).add(anyInt(), anyInt());
}
#Test
public void subtractFunctionTest() throws Exception {
Assert.assertNotNull(calculatorController);
Result expectedResult = new Result();
expectedResult.setResult(90);
when(calculatorService.subtract(anyInt(), anyInt())).thenReturn(expectedResult);
mockMvc.perform(get("/calculator/subtract").accept(MediaType.APPLICATION_JSON_VALUE)
.param("val1", "100")
.param("val2", "10"))
.andExpect(content().contentType("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", equalTo(90)));
verify(calculatorService, times(1)).subtract(anyInt(), anyInt());
}
The second test always seems to fail on assertions that either content type is not set or the expected result is wrong.
It seems like the response for the first test is somehow being evaluated in the second test and so is obviously wrong!
I know the controller and service work as expected and the exact same tests running with jUnit actually work ok.
I have managed to get the tests to perform properly only when I do the following:
#BeforeGroups("subtract")
public void reset() {
Mockito.reset(calculatorService);
mockMvc = MockMvcBuilders.standaloneSetup(calculatorController).build();
}
#Test(groups = "subtract")
public void subtractFunctionTest() throws Exception {
System.out.println("***** IN METHOD *****");
Assert.assertNotNull(calculatorController);
Result expectedResult = new Result();
expectedResult.setResult(90);
when(calculatorService.subtract(anyInt(), anyInt())).thenReturn(expectedResult);
//Perform HTTP Get for the homepage
mockMvc.perform(get("/calculator/subtract").accept(MediaType.APPLICATION_JSON_VALUE)
.param("val1", "100")
.param("val2", "10"))
.andExpect(content().contentType("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", equalTo(90)));
//Verify that the service method was only called one time
verify(calculatorService, times(1)).subtract(anyInt(), anyInt());
}
This means I need to add one of these reset methods for each test method though and I then need a group per test method which doesnt seem correct.
There is a difference in the behaviour of these frameworks:
JUnit creates a new instance of class for every of its test methods. This means that the fields are not shared between tests.
But TestNG creates only one object and thus the state in fields is shared between to #Tests
For Mockito you need to init mocks before every test method so that the state is not shared between two #Tests in TestNG:
#BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}
For JUnit it works out of box because 2nd #Test has its own fields and its own mocks.
I'm not sure, if this answer fits to the questioners problem, because the mocks are not listed. But I'd like to re-state one comment from andy in the accepted answer, which helped me out on the same problem. Here are some more details and an example:
public class B {
private A a;
B (A a) {
this.a = a
}
// ...
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MyTest {
#Mock
A a;
#InjectMocks
B B;
#BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
void test1() {
// ...
// b.toString();
// b.getA().toString();
// a.toString();
}
#Test
void test2() {
// ...
// b.toString();
// b.getA().toString();
// a.toString();
}
#AfterMethod
public void reset()
{
// Solution:
b = null;
}
}
Mockitos MockitoAnnotations.initMocks(this) only re-initializes mocks, as Mockito.reset(a) only resets mocks. The problem is the class under test, which is annotated with #InjectMocks. This class, here named B, is not initialized again. It is initialized for the first test with a mock of A, the mock of A is re-initialized but B still contains the first version of A.
The solution is to reset the class under test manually with b = null at any plausible place (#AfterMethod or before initMocks). Then Mockito also re-inizialized the class under test B.
You can also use MockitoTestNGListener it is similar to JUnit MockitoJUnitRunner or MockitoExtension.
Example of code:
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.util.Map;
#Listeners(MockitoTestNGListener.class)
public class MyTest {
#Mock
Map map;
#InjectMocks
SomeType someType;
#Test
void test() {
// ...
}
}

Resources