0. Punto de partida

Muchas de nuestras aplicaciones hacen llamadas a otros servicios externos lo cual es un problema cuando queremos testear nuestra aplicación ya que o bien dichos servicios pueden no responder como se espera (debido a que son entornos de desarrollo) o bien no queremos realizar invocaciones que supongan cambios en las aplicaciones externas durante la fase de testing.

Para evitar esta problemática debemos mockear nuestras invocaciones a todos los servicios externos para que devuelvan una respuesta concreta. SpringBoot tenemos a nuestra disposición diferentes mecanismos de mocking siendo MockWebServer el recomendado por Spring para hacer esta tarea y es el que explicaremos en esta guía.

Por lo general nuestro proyecto tendrá la configuración de los diferentes WebClient que use 

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }

    @Bean
    public WebClient webClient1(WebClient.Builder webClientBuilder) {
        return webClientBuilder.baseUrl("http://aplicacionexterna1.um.es/").build();
    }

    @Bean
    public WebClient webClient2(WebClient.Builder webClientBuilder) {
        return webClientBuilder.baseUrl("http://aplicacionexterna2.um.es/").build();
    }

}


Ésta configuración establece los clientes a los servicios externos y cada servicio usará internamente el que necesite. Por tanto desde los test no tendremos acceso directo a modificar ninguna url base, por lo que nuestro objetivo será sustituir los WebClient utilizados por unos nuevos que no ataquen al servicio real y, en este punto, es donde se aplica el sistema de mocking basado en MockWebServer.

1. Configuración

El primer paso para la configuración de nuestro MockWebServer es establecer las dependencias maven sólo para el scope de test. Si usamos el arquetipo fundewebjs-api-parent no hará falta establecer la versión de la librería a utilizar

<!-- Mock server -->
	<dependency>
	    <groupId>com.squareup.okhttp3</groupId>
	    <artifactId>okhttp</artifactId>
	    <scope>test</scope>
	</dependency>
	<dependency>
	    <groupId>com.squareup.okhttp3</groupId>
	    <artifactId>mockwebserver</artifactId>
	    <scope>test</scope>
	</dependency>

Una vez establecidas las dependencias deberé crearme una clase de configuración sólo para test que sobrescriba la configuración que tenga mi aplicación, para ello haremos uso de la anotacion @TestConfiguration creando una clase con dicha anotación, donde a su vez definiremos los nuevos WebClient de la siguiente manera:

@TestConfiguration
public class WebClientTestConfiguration {

	public static final int PORT = 1080;

	@Bean
	public WebClient.Builder webClientBuilder() {
		return WebClient.builder();
	}

	@Bean
	public WebClient webClient( WebClient.Builder webClientBuilder ) {
		return webClientBuilder.baseUrl( String.format( "http://localhost:%s", PORT ) ).defaultHeader( HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON ).
        defaultHeader( HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON ).build();
	}
}

En esta clase tenemos varios elementos a tener en cuenta: 

Una vez tenemos configurada nuestra configuración de test, con los WebClient preparados podemos comenzar la implementación de nuestra clase de test. Un ejemplo de implementación sería el siguiente

@TestPropertySource( locations = "classpath:application-test.properties" )
@ContextConfiguration( classes = {MiServivico.class} )
@SpringBootTest
@Import( WebClientTestConfiguration.class )
@TestInstance( TestInstance.Lifecycle.PER_CLASS )
class MiTestConServiciosExternosTest {


	@Autowired
	private MiServivico servicio;

	// private static MockServer mock;
	private static MockWebServer mockServer;


	// URLs
	private static final String LOGIN = "/public/recurso";
	

	@BeforeAll
	public void iniciaMockServer() {
		//Creamos una instancia de mockServer
		mockServer = new MockWebServer();
		try {
			//Arrancamos mockServer en el puerto que hemos indicado en la configuración
			mockServer.start( WebClientTestConfiguration.PORT );
		} catch ( IOException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@AfterAll
	static void finalizaMockServer() {
		if ( mockServer != null ) {
			// mockServer.stop();
			try {
				mockServer.shutdown();
			} catch ( IOException e ) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	@Test
	void miTest() throws Exception {
		
		String datosARecibir1 = "json1";
 		String datosARecibir2 = "json2"; 

		//Encolamos las respuestas a recibir
		mockServer.enqueue( new MockResponse().setBody(  datosARecibir1 ).addHeader( "Content-Type", "application/json" ) );
		mockServer.enqueue( new MockResponse().setBody(  datosARecibir2 ).addHeader( "Content-Type", "application/json" ) );

        String salida = servicio.metodoServicioConWebClient();
		assertThat( salida ).isEqualTo( "Respuesta esperada1" );
		salida = servicio.metodoServicioConWebClient();
		assertThat( salida ).isEqualTo( "Respuesta esperada2" );
    }
 ... 
 ...
}


Del código anterior los detalles importantes son

Con este código ya podremos emular llamadas a servicios externos y MockServer irá devolviendo las peticiones que tenga almacenadas. (advertencia) Hay que analizar cuidadosamente las respuestas que esperamos y el orden en el que deben llegar para que los test funcionen de manera correcta.