How to print only the requests of the failed test with @AutoConfigureMockMvc?
Asked Answered
I

2

3

In our project we are using @AutoConfigureMockMvc with printOnlyOnFailure left as default, true.

This works fine and prints none of the requests… except if any test fails. At that point, it prints all requests from ALL tests that have been executed before. Although this might sometimes be useful, this may print an enormous amount of log, and if it happens on our CI server, the log gets truncated and we cannot even see which test failed (since the AssertionError is printed afterwards.

Even worse: if multiple tests fail, all previous requests are printed for each failing test.

Is it possible to configure it such that it will only print the requests of the failing tests?

Here is a sample test to reproduce the problem:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void successfulTest() throws Exception {
        mockMvc.perform(get("/successfulTest"))
                .andExpect(status().isNotFound());
    }

    @Test
    public void failingTest() throws Exception {
        mockMvc.perform(get("/failingTest"))
                .andExpect(status().isOk());
    }

    @Test
    public void failingTest2() throws Exception {
        mockMvc.perform(get("/failingTest2"))
                .andExpect(status().isOk());
    }

    @Configuration
    static class TestApplication {
    }
}

We are using spring-boot 1.5.14 and Java 8. I also reproduced the problem with spring-boot 2.1.4.

From what I could find, the log lines are stored in org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer.DeferredLinesWriter.lines and never get reset, nor does there seem to be a way to reset it – and I'd prefer to avoid doing it through reflexion.

Inbreed answered 24/5, 2019 at 14:33 Comment(1)
Yea, I'm running into this too. Super annoying to have a single test failure spit out thousands of lines of unrelated mock request code.Beeeater
I
2

So I tried to dive a bit more into the SpringBootMockMvcBuilderCustomizer implementation and everything appears to be private or protected, preventing to reuse anything (not even sure you can reuse the PrintingResultHandler itself, which you are forced to extend since the only constructor is protected).

Using reflection is not too hard though, add this to the test:

@Autowired
private ApplicationContext context;
private Runnable linesWriterClearer;

@PostConstruct
public void initLinesWriterClearer() throws NoSuchFieldException, IllegalAccessException {
    Object linesWriter = context.getBean("org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter");
    Field linesField = linesWriter.getClass().getDeclaredField("lines");
    linesField.setAccessible(true);
    List<?> lines = (List<?>) linesField.get(linesWriter);
    linesWriterClearer = lines::clear;
}

@Before
public void clearLinesWriter() {
    linesWriterClearer.run();
}

Not very clean and may probably break in a future version of Spring Boot, but it works.

I would hope a better solution could be implemented but I'm afraid it won't be possible.

Inbreed answered 24/5, 2019 at 16:20 Comment(0)
W
1

I’m unsure if anyone is still interested into this but I remember I did this few years ago on another non Spring boot project and probably before the printOnlyOnFailure option was introduced but I don’t really understand why it prints every previous executed tests on v5.0.5 (I haven’t tested higher versions)

Like you I need it back on a newer project today, it took me some hours to remind how I did this, I think I found the original idea on stackoverflow too, so here is the short answer as an attempt to resurrect this excellent idea (I’m currently not in front of the sources but feel free to ask the implementation, it is possible with just Junit4 or Junit5 without reflection)

Short answer :

  • Register a Junit5 TestWatcher
  • Override the testFailed() method
  • When your JUnit test executes, store the mockmvc’s ResultActions
  • On failure, call print().handle(result.andReturn())
  • Annotate your test Abstract/Config with @AutoConfigureMockMvc(print = MockMvcPrint.NONE)

resultActions being the instance the last test failure saved.

The downside is that you have to write your test like that

result = mvc
    .perform();
result
    .andExpect()
    .andExpect();

Not perfect but I find this way more elegant than using reflection :)

Whist answered 14/3, 2022 at 22:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.