Why is my Integration Test trying to setup a new jetty instance?

830 Views Asked by At

I have a project with 3 integration tests classes: A, B and C. I made a change in the code, and as part of those changes I added a @MockBean to test class A.

Here is a class that is extended by every Integration Test class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyApplication.class, webEnvironment = RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.yml")
@ActiveProfiles(profiles = {"default", "test"})
public abstract class IntegrationTest {

    @Value("${local.server.port}")
    private int serverPort;

    @Autowired
    private ObjectMapper objectMapper;

    @Before
    public void setUpIntegrationTest() {
        RestAssured.port = serverPort;
        RestAssured.config = RestAssuredConfig.config()
                .logConfig(LogConfig.logConfig()
                        .enableLoggingOfRequestAndResponseIfValidationFails()
                        .enablePrettyPrinting(true))
                .objectMapperConfig(objectMapperConfig()
                        .jackson2ObjectMapperFactory((cls, charset) -> objectMapper)
                        .defaultObjectMapperType(ObjectMapperType.JACKSON_2))
                .jsonConfig(jsonConfig().numberReturnType(BIG_DECIMAL))
                .redirect(new RedirectConfig().followRedirects(false));
    }
}

Now for a concrete test class:

import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;

public class TestClassA extends IntegrationTest {
    @MockBean
    private SomeBean foo;

    @Before
    @Override
    public void setUpIntegrationTest() {
        super.setUpIntegrationTest();
        doNothing().when(foo).fooMethod(any(SomeClass.class), any(SomeOtherClass.class));
    }

    @Test
    public void testCaseX() {
        given()
            .body("{\"foo\": \"bar\"}")
        .when()
            .post("/some/path/")
        .then()
            .statusCode(OK.value());
    }
}

I have tried to run tests in three different ways:

  1. Run only test class A, with the mocked bean. All tests pass.
  2. Build the project which runs all test classes. Test classes B and C pass, but A fails during application context loading while trying to start a jetty instance and fails because the address is already in use.

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.github.azagniotov.stubby4j.server.StubbyManager]: Factory method 'stubby' threw exception; nested exception is java.net.BindException: Address already in use Caused by: java.net.BindException: Address already in use

  1. Remove the mocked bean, and build the project. Test classes B and C pass. Test class A successfully loads the application context, but some tests fail (due to the missing behaviour given by the mock).

Jetty is setup as part of Stubby4j and it is instantiated as a configuration bean in the following way:

@Configuration
public class StubbyConfig {

    @Bean
    public StubbyManager stubby(final ResourceLoader resourceLoader) throws Exception {

        Resource stubbyFile = resourceLoader.getResource("classpath:stubs/stubby.yml");

        if (stubbyFile.exists()) {
            Map<String, String> stubbyConfig = Maps.newHashMap();
            stubbyConfig.put("disable_admin_portal", null);
            stubbyConfig.put("disable_ssl", null);

            File configFile = stubbyFile.getFile();
            Future<List<StubHttpLifecycle>> stubLoadComputation =
                    ConcurrentUtils.constantFuture(new YAMLParser().parse(configFile.getParent(), configFile));

            StubbyManager stubbyManager = new StubbyManagerFactory()
                    .construct(configFile, stubbyConfig, stubLoadComputation);
            stubbyManager.startJetty();

            return stubbyManager;
        } else {
            throw new FileNotFoundException("Could not load stubby.yml");
        }
    }
}

I did some debugging in two different ways, putting a break point in the line stubbyManager.startJetty();:

  1. Running just test class A. Execution stopped in the break point only once.
  2. Running test class A with some other test class (for example, B). Execution stopped only once for B, but twice for A. The second time it failed with the aforementioned error.
  3. Again, if I remove the mocked bean and run multiple test classes the execution only stops at that line once per test class.

My question, obviously, is: why does the MockedBean annotation cause this behaviour, and how can I avoid it?

Thanks in advance.

Current project setup:

  • Spring Boot version 1.4.2.RELEASE
  • Stubby4j version 4.0.4
2

There are 2 best solutions below

1
On

I just start Jetty when not already done with:

if(!stubbyManager.statuses().contains(""Stubs portal configured at")
    stubbyManager.startJetty();
else
    stubbyManager.joinJetty();

Let me know if you find a better solution

0
On

You could use Spring's SocketUtils class to find available TCP port, which then you could pass into your stubbyConfig map:

...
static final int PORT_RANGE_MIN = 2048;
static final int PORT_RANGE_MAX = 65535;
...
...
public StubbyConfig() {
  this.stubPort = SocketUtils.findAvailableTcpPort(PORT_RANGE_MIN, PORT_RANGE_MAX);
}
...
@Bean
public StubbyManager stubby(final ResourceLoader resourceLoader) throws Exception {
    ...
    if (stubbyFile.exists()) {
       ...
       stubbyConfig.put("stubs", String.valueOf(stubsPort));
       ...
    }
}
...
public int getStubsPort() {
  return this.stubPort;
}

Then, because you have the port getter on your StubbyConfig class, you could pass it to the RestAssured.port when running the integration test