okhttp icon indicating copy to clipboard operation
okhttp copied to clipboard

MockWebServer: Clear requests before each test

Open GabrielBB opened this issue 5 years ago • 18 comments

Can we have a method to clear requests? Something like this:

    @BeforeEach
    fun cleanRequests() {
        mockServer.clearRequests();
    }

My tests are taking requests from previous tests. I wouldn't want to shutdown entirely

GabrielBB avatar Aug 05 '20 11:08 GabrielBB

Are you using the test rule? There should be a new instance for every test.

JakeWharton avatar Aug 05 '20 12:08 JakeWharton

Yes, I'm using @TestInstance(TestInstance.Lifecycle.PER_CLASS)

GabrielBB avatar Aug 05 '20 13:08 GabrielBB

Can you switch to PER_METHOD? Assuming such a thing exists.

JakeWharton avatar Aug 05 '20 13:08 JakeWharton

Yeah, MockWebServer instances are not expected to be reused.

swankjesse avatar Aug 05 '20 15:08 swankjesse

Will a clear method be enough to be reusable ? I can open PR, why not I guess ?

GabrielBB avatar Aug 05 '20 18:08 GabrielBB

No, I don't think it's a good fit for a PR. The clear method encourages a pattern we don't support well.

swankjesse avatar Aug 05 '20 23:08 swankjesse

@JakeWharton @swankjesse What if we need an static MockWebServer in order to register @DynamicPropertySource properties with the mockWebServer.getPort()? In that case we need a single instance and resetting it, right?

nightswimmings avatar Nov 16 '21 10:11 nightswimmings

Can't you change your logic to update the DynamicPropertyRegistry as each test runs with the new port/url?

yschimke avatar Nov 16 '21 11:11 yschimke

DynamicPropertyRegistry method must be static as per documentation, so I guess it does not reevaluate within the class. I think that would imply restarting the whole spring context since the mechanism is similar to that of @SpringBootTest(properties=X). So we'd need the mockrestserver instance to be static as well, I am not sure it makes sense to reinstantiate it globally and start/shutwoning on each test, I find the clearRequests() approach more comfy for that scenario

nightswimmings avatar Nov 16 '21 16:11 nightswimmings

The method is static, but it provides a Supplier that gets called on demand. I'm not saying it's ideal for you, just that it doesn't appear that you are blocked as you could update the port each time a new MockWebServer is started for your test.

yschimke avatar Nov 16 '21 16:11 yschimke

I don't understand the Supplier part but I am interested.. :)

I have this currently, how would you exactly do it?

     static MockWebServer mockWebServer;

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry propsRegistry) {
         propsRegistry.add("spring.cloud.gateway.routes[0].uri", () -> "http://localhost:" + mockWebServer.getPort())
    }
    
    @BeforeAll
    static void beforeAll() throws IOException {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
    }

    @AfterAll
    static void afterAll() throws IOException {
        mockWebServer.shutdown();
    }

nightswimmings avatar Nov 16 '21 17:11 nightswimmings

Maybe something like the following, where port should be read everytime the property is accessed.

     MockWebServer mockWebServer;
     static int port;

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry propsRegistry) {
         propsRegistry.add("spring.cloud.gateway.routes[0].uri", () -> "http://localhost:" + port)
    }
    
    @Before
    void before() throws IOException {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
    }

    @After
    void afterAll() throws IOException {
        mockWebServer.shutdown();
    }

yschimke avatar Nov 16 '21 18:11 yschimke

Ahh I think I understand you. @DynamicPropertySource is only evaluated once, but by your words I understand that the property supplier itself does it every time the property is accessed. If so I guess your example is missing a port = mockWebServer.getPort() at the end of the before(), right?

nightswimmings avatar Nov 17 '21 11:11 nightswimmings

@yschimke I did some tests and the evaluation for the supplier is done once at context startup and that occurs before the BeforeEach, so even if we managed to tell spring to reevaluate whole context before each test, I should make sure that the BeforeEach is still executed before the property evaluation, otherwise I need to do an ugly hack for reinitializing the mockwebserver on the supplier only as long as it takes part during the same test. Frankly I still see clearRequest() a much nicer approach (and more performant)

nightswimmings avatar Nov 17 '21 11:11 nightswimmings

Is it possible the item reading the property is in the wrong scope? Singleton?

https://www.baeldung.com/spring-dynamicpropertysource

If our PostgreSQL container is going to listen to a random port every time, then we should somehow set and change the spring.datasource.url configuration property dynamically. Basically, each test should have its own version of that configuration property.

yschimke avatar Nov 20 '21 18:11 yschimke

May I ask if there is any solution here? Is okttp Mockserver just unusable with Spring tests and their lifecycle when a @DynamicPropertiesSource is used in combination with a static instance? I have the same problem as stated above and I actually don't understand why the queue can't just be cleared via API.

tberger-plugsurfing avatar Jan 18 '24 14:01 tberger-plugsurfing

Sharing state between tests is problematic, especially since MockWebServer isn’t dynamic on features like whether TLS is enabled.

The ‘more performant’ argument is a rationalization, not a reason; MockWebServer performance is completely reasonable without reusing instances.

I’m reopening this to find a way to use MockWebServer despite Spring Boot’s limitations. It might be something as basic as providing a factory of MockWebServer instances.

swankjesse avatar Jan 19 '24 12:01 swankjesse

As a workaround we can find random port before all tests and use it for all MockWebServer instances:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(profiles = { "test" })
@EnableAutoConfiguration
class ApiServerTests {
  static final String mockBackEndHost = "localhost";
  static final int mockBackEndPort = TestSocketUtils.findAvailableTcpPort();

  @DynamicPropertySource
  static void setupConfig(DynamicPropertyRegistry r) {
    r.add("api.server.url", () -> {
      return String.format("http://%s:%s/api", mockBackEndHost, mockBackEndPort);
    });
  }

  MockWebServer mockBackEndServer;

  @BeforeEach
  void setUp() throws IOException {
    mockBackEndServer= new MockWebServer();
    mockBackEndServer.start(InetAddress.getByName(mockBackEndHost), mockBackEndPort);
  }

  @AfterEach
  void tearDown() {
    try {
      mockBackEndServer.close();
    } catch(Exception e) {
    }
  }
  ...
}

As the result we reach our goals:

  1. use clean MockWebServer instance for each test
  2. use DynamicPropertySource for context configuration

anatoly-spb avatar May 15 '24 07:05 anatoly-spb