rewrite unit tests to work with WireMock instead of class manipulation

This commit is contained in:
Stefan Kalscheuer 2020-05-04 18:05:08 +02:00
parent 706ff495e2
commit a13dd7a194
23 changed files with 155 additions and 212 deletions

17
pom.xml
View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.stklcode.pubtrans</groupId> <groupId>de.stklcode.pubtrans</groupId>
@ -65,15 +64,9 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.bytebuddy</groupId> <groupId>com.github.tomakehurst</groupId>
<artifactId>byte-buddy</artifactId> <artifactId>wiremock</artifactId>
<version>1.10.10</version> <version>2.26.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.10</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -16,23 +16,23 @@
package de.stklcode.pubtrans.ura; package de.stklcode.pubtrans.ura;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import de.stklcode.pubtrans.ura.model.Message; import de.stklcode.pubtrans.ura.model.Message;
import de.stklcode.pubtrans.ura.model.Stop; import de.stklcode.pubtrans.ura.model.Stop;
import de.stklcode.pubtrans.ura.model.Trip; import de.stklcode.pubtrans.ura.model.Trip;
import net.bytebuddy.ByteBuddy; import org.junit.jupiter.api.AfterAll;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static net.bytebuddy.implementation.MethodDelegation.to; import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
@ -46,29 +46,29 @@ import static org.hamcrest.core.Is.is;
* @author Stefan Kalscheuer * @author Stefan Kalscheuer
*/ */
public class UraClientTest { public class UraClientTest {
// Mocked resource URL and exception message. private static WireMockServer httpMock;
private static String mockResource = null;
private static String mockException = null;
@BeforeAll @BeforeAll
public static void initByteBuddy() { public static void setUp() {
// Install ByteBuddy Agent. // Initialize HTTP mock.
ByteBuddyAgent.install(); httpMock = new WireMockServer(WireMockConfiguration.options().dynamicPort());
httpMock.start();
WireMock.configureFor("localhost", httpMock.port());
}
new ByteBuddy().redefine(UraClient.class) @AfterAll
.method(named("request")) public static void tearDown() {
.intercept(to(UraClientTest.class)) httpMock.stop();
.make() httpMock = null;
.load(UraClient.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
} }
@Test @Test
public void getStopsTest() { public void getStopsTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V2_stops.txt"); mockHttpToFile(2, "instant_V2_stops.txt");
// List stops and verify some values. // List stops and verify some values.
List<Stop> stops = new UraClient("mocked").getStops(); List<Stop> stops = new UraClient(httpMock.baseUrl(), "/interfaces/ura/instant_V2", "/interfaces/ura/stream").getStops();
assertThat(stops, hasSize(10)); assertThat(stops, hasSize(10));
assertThat(stops.get(0).getId(), is("100210")); assertThat(stops.get(0).getId(), is("100210"));
assertThat(stops.get(1).getName(), is("Brockenberg")); assertThat(stops.get(1).getName(), is("Brockenberg"));
@ -77,36 +77,26 @@ public class UraClientTest {
assertThat(stops.get(4).getLongitude(), is(6.0708663)); assertThat(stops.get(4).getLongitude(), is(6.0708663));
// Test Exception handling. // Test Exception handling.
mockHttpToException("Provoked Exception 1"); mockHttpToError(500);
try { try {
new UraClient("mocked").getStops(); new UraClient(httpMock.baseUrl()).getStops();
} catch (RuntimeException e) { } catch (RuntimeException e) {
assertThat(e, is(instanceOf(IllegalStateException.class))); assertThat(e, is(instanceOf(IllegalStateException.class)));
assertThat(e.getCause(), is(instanceOf(IOException.class))); assertThat(e.getCause(), is(instanceOf(IOException.class)));
assertThat(e.getCause().getMessage(), is("Provoked Exception 1")); assertThat(e.getCause().getMessage(), startsWith("Server returned HTTP response code: 500 for URL"));
} }
} }
public static InputStream request(String originalURL) throws IOException {
if (mockResource == null && mockException != null) {
IOException e = new IOException(mockException);
mockException = null;
throw e;
}
InputStream res = UraClientTest.class.getResourceAsStream(mockResource);
mockResource = null;
return res;
}
@Test @Test
public void getStopsForLineTest() { public void getStopsForLineTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V2_stops_line.txt"); mockHttpToFile(2, "instant_V2_stops_line.txt");
// List stops and verify some values. // List stops and verify some values.
List<Stop> stops = new UraClient("mocked").forLines("33").getStops(); List<Stop> stops = new UraClient(httpMock.baseUrl(), "/interfaces/ura/instant_V2", "/interfaces/ura/stream")
.forLines("33")
.getStops();
assertThat(stops, hasSize(47)); assertThat(stops, hasSize(47));
assertThat(stops.get(0).getId(), is("100000")); assertThat(stops.get(0).getId(), is("100000"));
assertThat(stops.get(1).getName(), is("Kuckelkorn")); assertThat(stops.get(1).getName(), is("Kuckelkorn"));
@ -119,10 +109,10 @@ public class UraClientTest {
@Test @Test
public void getStopsForPositionTest() { public void getStopsForPositionTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_stops_circle.txt"); mockHttpToFile(1, "instant_V1_stops_circle.txt");
// List stops and verify some values. // List stops and verify some values.
List<Stop> stops = new UraClient("mocked") List<Stop> stops = new UraClient(httpMock.baseUrl())
.forPosition(51.51009, -0.1345734, 200) .forPosition(51.51009, -0.1345734, 200)
.getStops(); .getStops();
assertThat(stops, hasSize(13)); assertThat(stops, hasSize(13));
@ -133,8 +123,8 @@ public class UraClientTest {
assertThat(stops.get(4).getLongitude(), is(-0.134172)); assertThat(stops.get(4).getLongitude(), is(-0.134172));
assertThat(stops.get(5).getIndicator(), is(nullValue())); assertThat(stops.get(5).getIndicator(), is(nullValue()));
mockHttpToFile("instant_V1_stops_circle_name.txt"); mockHttpToFile(1, "instant_V1_stops_circle_name.txt");
stops = new UraClient("mocked") stops = new UraClient(httpMock.baseUrl())
.forStopsByName("Piccadilly Circus") .forStopsByName("Piccadilly Circus")
.forPosition(51.51009, -0.1345734, 200) .forPosition(51.51009, -0.1345734, 200)
.getStops(); .getStops();
@ -145,16 +135,16 @@ public class UraClientTest {
@Test @Test
public void getTripsForDestinationNamesTest() { public void getTripsForDestinationNamesTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_destination.txt"); mockHttpToFile(1, "instant_V1_trips_destination.txt");
// List stops and verify some values. // List stops and verify some values.
List<Trip> trips = new UraClient("mocked").forDestinationNames("Piccadilly Circus").getTrips(); List<Trip> trips = new UraClient(httpMock.baseUrl()).forDestinationNames("Piccadilly Circus").getTrips();
assertThat(trips, hasSize(9)); assertThat(trips, hasSize(9));
assertThat(trips.stream().filter(t -> !t.getDestinationName().equals("Piccadilly Cir")).findAny(), assertThat(trips.stream().filter(t -> !t.getDestinationName().equals("Piccadilly Cir")).findAny(),
is(Optional.empty())); is(Optional.empty()));
mockHttpToFile("instant_V1_trips_stop_destination.txt"); mockHttpToFile(1, "instant_V1_trips_stop_destination.txt");
trips = new UraClient("mocked") trips = new UraClient(httpMock.baseUrl())
.forStops("156") .forStops("156")
.forDestinationNames("Marble Arch") .forDestinationNames("Marble Arch")
.getTrips(); .getTrips();
@ -168,14 +158,14 @@ public class UraClientTest {
@Test @Test
public void getTripsTowardsTest() { public void getTripsTowardsTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_towards.txt"); mockHttpToFile(1, "instant_V1_trips_towards.txt");
/* List stops and verify some values */ /* List stops and verify some values */
List<Trip> trips = new UraClient("mocked").towards("Marble Arch").getTrips(); List<Trip> trips = new UraClient(httpMock.baseUrl()).towards("Marble Arch").getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
mockHttpToFile("instant_V1_trips_stop_towards.txt"); mockHttpToFile(1, "instant_V1_trips_stop_towards.txt");
trips = new UraClient("mocked").forStops("156").towards("Marble Arch").getTrips(); trips = new UraClient(httpMock.baseUrl()).forStops("156").towards("Marble Arch").getTrips();
assertThat(trips, hasSize(17)); assertThat(trips, hasSize(17));
assertThat(trips.stream().filter(t -> !t.getStop().getId().equals("156")).findAny(), is(Optional.empty())); assertThat(trips.stream().filter(t -> !t.getStop().getId().equals("156")).findAny(), is(Optional.empty()));
} }
@ -183,10 +173,10 @@ public class UraClientTest {
@Test @Test
public void getTripsTest() { public void getTripsTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_all.txt"); mockHttpToFile(1, "instant_V1_trips_all.txt");
// Get trips without filters and verify some values. // Get trips without filters and verify some values.
List<Trip> trips = new UraClient("mocked").getTrips(); List<Trip> trips = new UraClient(httpMock.baseUrl()).getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
assertThat(trips.get(0).getId(), is("27000165015001")); assertThat(trips.get(0).getId(), is("27000165015001"));
assertThat(trips.get(1).getLineID(), is("55")); assertThat(trips.get(1).getLineID(), is("55"));
@ -200,10 +190,11 @@ public class UraClientTest {
assertThat(trips.get(9).getStop().getId(), is("100002")); assertThat(trips.get(9).getStop().getId(), is("100002"));
// Repeat test for API V2. // Repeat test for API V2.
mockHttpToFile("instant_V2_trips_all.txt"); mockHttpToFile(2, "instant_V2_trips_all.txt");
// Get trips without filters and verify some values. // Get trips without filters and verify some values.
trips = new UraClient("mocked").getTrips(); trips = new UraClient(httpMock.baseUrl(), "/interfaces/ura/instant_V2", "/interfaces/ura/stream")
.getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
assertThat(trips.get(0).getId(), is("27000165015001")); assertThat(trips.get(0).getId(), is("27000165015001"));
assertThat(trips.get(1).getLineID(), is("55")); assertThat(trips.get(1).getLineID(), is("55"));
@ -217,28 +208,28 @@ public class UraClientTest {
assertThat(trips.get(9).getStop().getId(), is("100002")); assertThat(trips.get(9).getStop().getId(), is("100002"));
// Get limited number of trips. // Get limited number of trips.
mockHttpToFile("instant_V1_trips_all.txt"); mockHttpToFile(1, "instant_V1_trips_all.txt");
trips = new UraClient("mocked").getTrips(5); trips = new UraClient(httpMock.baseUrl()).getTrips(5);
assertThat(trips, hasSize(5)); assertThat(trips, hasSize(5));
// Test mockException handling. // Test mockException handling.
mockHttpToException("Provoked mockException 2"); mockHttpToError(502);
try { try {
new UraClient("mocked").getTrips(); new UraClient(httpMock.baseUrl()).getTrips();
} catch (RuntimeException e) { } catch (RuntimeException e) {
assertThat(e, is(instanceOf(IllegalStateException.class))); assertThat(e, is(instanceOf(IllegalStateException.class)));
assertThat(e.getCause(), is(instanceOf(IOException.class))); assertThat(e.getCause(), is(instanceOf(IOException.class)));
assertThat(e.getCause().getMessage(), is("Provoked mockException 2")); assertThat(e.getCause().getMessage(), startsWith("Server returned HTTP response code: 502 for URL"));
} }
} }
@Test @Test
public void getTripsForStopTest() { public void getTripsForStopTest() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_stop.txt"); mockHttpToFile(1, "instant_V1_trips_stop.txt");
// Get trips for stop ID 100000 (Aachen Bushof) and verify some values. // Get trips for stop ID 100000 (Aachen Bushof) and verify some values.
List<Trip> trips = new UraClient("mocked") List<Trip> trips = new UraClient(httpMock.baseUrl())
.forStops("100000") .forStops("100000")
.getTrips(); .getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
@ -249,8 +240,8 @@ public class UraClientTest {
assertThat(trips.get(3).getStop().getIndicator(), is("H.15")); assertThat(trips.get(3).getStop().getIndicator(), is("H.15"));
// Get trips for stop name "Uniklinik" and verify some values. // Get trips for stop name "Uniklinik" and verify some values.
mockHttpToFile("instant_V1_trips_stop_name.txt"); mockHttpToFile(1, "instant_V1_trips_stop_name.txt");
trips = new UraClient("mocked") trips = new UraClient(httpMock.baseUrl())
.forStopsByName("Uniklinik") .forStopsByName("Uniklinik")
.getTrips(); .getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
@ -265,10 +256,10 @@ public class UraClientTest {
@Test @Test
public void getTripsForLine() { public void getTripsForLine() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_line.txt"); mockHttpToFile(1, "instant_V1_trips_line.txt");
// Get trips for line ID 3 and verify some values. // Get trips for line ID 3 and verify some values.
List<Trip> trips = new UraClient("mocked") List<Trip> trips = new UraClient(httpMock.baseUrl())
.forLines("3") .forLines("3")
.getTrips(); .getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
@ -279,8 +270,8 @@ public class UraClientTest {
assertThat(trips.get(3).getStop().getIndicator(), is("H.4 (Pontwall)")); assertThat(trips.get(3).getStop().getIndicator(), is("H.4 (Pontwall)"));
// Get trips for line name "3.A" and verify some values. // Get trips for line name "3.A" and verify some values.
mockHttpToFile("instant_V1_trips_line_name.txt"); mockHttpToFile(1, "instant_V1_trips_line_name.txt");
trips = new UraClient("mocked") trips = new UraClient(httpMock.baseUrl())
.forLinesByName("3.A") .forLinesByName("3.A")
.getTrips(); .getTrips();
assertThat(trips, hasSize(10)); assertThat(trips, hasSize(10));
@ -291,8 +282,8 @@ public class UraClientTest {
assertThat(trips.get(3).getStop().getName(), is("Aachen Gartenstraße")); assertThat(trips.get(3).getStop().getName(), is("Aachen Gartenstraße"));
// Get trips for line 3 with direction 1 and verify some values. // Get trips for line 3 with direction 1 and verify some values.
mockHttpToFile("instant_V1_trips_line_direction.txt"); mockHttpToFile(1, "instant_V1_trips_line_direction.txt");
trips = new UraClient("mocked") trips = new UraClient(httpMock.baseUrl())
.forLines("412") .forLines("412")
.forDirection(2) .forDirection(2)
.getTrips(); .getTrips();
@ -301,8 +292,8 @@ public class UraClientTest {
assertThat(trips.stream().filter(t -> !t.getDirectionID().equals(2)).findAny(), is(Optional.empty())); assertThat(trips.stream().filter(t -> !t.getDirectionID().equals(2)).findAny(), is(Optional.empty()));
// Test lineID and direction in different order. // Test lineID and direction in different order.
mockHttpToFile("instant_V1_trips_line_direction.txt"); mockHttpToFile(1, "instant_V1_trips_line_direction.txt");
trips = new UraClient("mocked") trips = new UraClient(httpMock.baseUrl())
.forDirection(2) .forDirection(2)
.forLines("412") .forLines("412")
.getTrips(); .getTrips();
@ -314,10 +305,10 @@ public class UraClientTest {
@Test @Test
public void getTripsForStopAndLine() { public void getTripsForStopAndLine() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_trips_stop_line.txt"); mockHttpToFile(1, "instant_V1_trips_stop_line.txt");
// Get trips for line ID 25 and 25 at stop 100000 and verify some values. // Get trips for line ID 25 and 25 at stop 100000 and verify some values.
List<Trip> trips = new UraClient("mocked") List<Trip> trips = new UraClient(httpMock.baseUrl())
.forLines("25", "35") .forLines("25", "35")
.forStops("100000") .forStops("100000")
.getTrips(); .getTrips();
@ -335,10 +326,10 @@ public class UraClientTest {
@Test @Test
public void getMessages() { public void getMessages() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V1_messages.txt"); mockHttpToFile(1, "instant_V1_messages.txt");
// Get messages without filter and verify some values. // Get messages without filter and verify some values.
List<Message> messages = new UraClient("mocked") List<Message> messages = new UraClient(httpMock.baseUrl())
.getMessages(); .getMessages();
assertThat(messages, hasSize(2)); assertThat(messages, hasSize(2));
assertThat(messages.get(0).getStop().getId(), is("100707")); assertThat(messages.get(0).getStop().getId(), is("100707"));
@ -354,10 +345,10 @@ public class UraClientTest {
@Test @Test
public void getMessagesForStop() { public void getMessagesForStop() {
// Mock the HTTP call. // Mock the HTTP call.
mockHttpToFile("instant_V2_messages_stop.txt"); mockHttpToFile(2, "instant_V2_messages_stop.txt");
// Get trips for stop ID 100707 (Berensberger Str.) and verify some values. // Get trips for stop ID 100707 (Berensberger Str.) and verify some values.
List<Message> messages = new UraClient("mocked") List<Message> messages = new UraClient(httpMock.baseUrl(), "/interfaces/ura/instant_V2", "/interfaces/ura/stream")
.forStops("100707") .forStops("100707")
.getMessages(); .getMessages();
assertThat(messages, hasSize(1)); assertThat(messages, hasSize(1));
@ -369,11 +360,19 @@ public class UraClientTest {
} }
private static void mockHttpToFile(String newResourceFile) { private static void mockHttpToFile(int version, String resourceFile) {
mockResource = newResourceFile; WireMock.stubFor(
get(urlPathEqualTo("/interfaces/ura/instant_V" + version)).willReturn(
aResponse().withBodyFile(resourceFile)
)
);
} }
private static void mockHttpToException(String newException) { private static void mockHttpToError(int code) {
mockException = newException; WireMock.stubFor(
get(anyUrl()).willReturn(
aResponse().withStatus(code)
)
);
} }
} }

View File

@ -16,51 +16,59 @@
package de.stklcode.pubtrans.ura.reader; package de.stklcode.pubtrans.ura.reader;
import de.stklcode.pubtrans.ura.UraClientTest; import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.ChunkedDribbleDelay;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import de.stklcode.pubtrans.ura.model.Trip; import de.stklcode.pubtrans.ura.model.Trip;
import net.bytebuddy.ByteBuddy; import org.junit.jupiter.api.AfterAll;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.*; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Collections; import java.util.Collections;
import java.util.Deque; import java.util.Deque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static net.bytebuddy.implementation.MethodDelegation.to; import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue;
/** /**
* Unit test for the asynchronous URA Trip reader. * Unit test for the asynchronous URA Trip reader.
* <p>
* Because this test runs asynchronously, it might not work as expected in debugging environments.
* Stream input files are normalized to equal line length and split into chunks, one line each 500ms.
* *
* @author Stefan Kalscheuer * @author Stefan Kalscheuer
*/ */
public class AsyncUraTripReaderTest { public class AsyncUraTripReaderTest {
private static final Queue<String> MOCK_LINES = new ArrayDeque<>(); private static WireMockServer httpMock;
private static PipedOutputStream mockOutputStream = new PipedOutputStream();
@BeforeAll @BeforeAll
public static void initByteBuddy() { public static void setUp() {
// Install ByteBuddy Agent. // Initialize HTTP mock.
ByteBuddyAgent.install(); httpMock = new WireMockServer(WireMockConfiguration.options().dynamicPort()
.asynchronousResponseEnabled(true)
.extensions(StreamTransformer.class)
);
httpMock.start();
WireMock.configureFor("localhost", httpMock.port());
}
// Mock the URL.openStream() call. @AfterAll
new ByteBuddy().redefine(AsyncUraTripReader.class) public static void tearDown() {
.method(named("getInputStream")) httpMock.stop();
.intercept(to(AsyncUraTripReaderTest.class)) httpMock = null;
.make()
.load(AsyncUraTripReader.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
} }
/** /**
@ -80,10 +88,10 @@ public class AsyncUraTripReaderTest {
Deque<Trip> trips = new ConcurrentLinkedDeque<>(); Deque<Trip> trips = new ConcurrentLinkedDeque<>();
// Start with V1 data and read file to mock list. // Start with V1 data and read file to mock list.
readLinesToMock(UraClientTest.class.getResource("stream_V1_stops_all.txt")); readLinesToMock(1, "/__files/stream_V1_stops_all.txt", 8);
AsyncUraTripReader tr = new AsyncUraTripReader( AsyncUraTripReader tr = new AsyncUraTripReader(
UraClientTest.class.getResource("stream_V1_stops_all.txt"), new URL(httpMock.baseUrl() + "/interfaces/ura/stream_V1"),
Collections.singletonList( Collections.singletonList(
trip -> { trip -> {
trips.add(trip); trips.add(trip);
@ -98,38 +106,22 @@ public class AsyncUraTripReaderTest {
TimeUnit.SECONDS.sleep(1); TimeUnit.SECONDS.sleep(1);
assumeTrue(trips.isEmpty(), "Trips should empty after 1s without reading"); assumeTrue(trips.isEmpty(), "Trips should empty after 1s without reading");
// Now write a single line to the stream pipe. // Wait another 1s for the callback to be triggered.
assumeTrue(writeNextLine(), "First line (version info) should be written"); TimeUnit.SECONDS.sleep(1);
assumeTrue(writeNextLine(), "Second line (first record) should be written");
// Wait up to 1s for the callback to be triggered. assertThat("Unexpected number of trips after first entry", trips.size(), is(2));
int i = 10;
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
assertThat("Unexpected number of trips after first entry", trips.size(), is(1));
// Flush all remaining lines. // Flush all remaining lines.
while (writeNextLine()) { TimeUnit.SECONDS.sleep(3);
TimeUnit.MILLISECONDS.sleep(10);
}
i = 10;
counter.set(0);
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
tr.close();
assertThat("Unexpected number of trips after all lines have been flushed", trips.size(), is(7)); assertThat("Unexpected number of trips after all lines have been flushed", trips.size(), is(7));
// Clear trip list and repeat with V2 data. // Clear trip list and repeat with V2 data.
trips.clear(); trips.clear();
readLinesToMock(UraClientTest.class.getResource("stream_V2_stops_all.txt")); readLinesToMock(2, "/__files/stream_V2_stops_all.txt", 8);
tr = new AsyncUraTripReader( tr = new AsyncUraTripReader(
UraClientTest.class.getResource("stream_V2_stops_all.txt"), new URL(httpMock.baseUrl() + "/interfaces/ura/stream_V2"),
Collections.singletonList(trips::add) Collections.singletonList(trips::add)
); );
@ -139,35 +131,20 @@ public class AsyncUraTripReaderTest {
TimeUnit.SECONDS.sleep(1); TimeUnit.SECONDS.sleep(1);
assumeTrue(trips.isEmpty(), "Trips should empty after 1s without reading"); assumeTrue(trips.isEmpty(), "Trips should empty after 1s without reading");
assumeTrue(writeNextLine(), "First line of v2 (version info) should be written"); TimeUnit.SECONDS.sleep(1);
assumeTrue(writeNextLine(), "Second line of v2 (first record) should be written"); assertThat("Unexpected number of v2 trips after first entry", trips.size(), is(2));
i = 10;
counter.set(0);
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
assertThat("Unexpected number of v2 trips after first entry", trips.size(), is(1));
// Add a second consumer that pushes to another list. // Add a second consumer that pushes to another list.
Deque<Trip> trips2 = new ConcurrentLinkedDeque<>(); Deque<Trip> trips2 = new ConcurrentLinkedDeque<>();
tr.addConsumer(trips2::add); tr.addConsumer(trips2::add);
// Flush all remaining lines. // Flush all remaining lines.
while (writeNextLine()) { TimeUnit.SECONDS.sleep(3);
TimeUnit.MILLISECONDS.sleep(10);
}
i = 10;
counter.set(0);
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
tr.close(); tr.close();
assertThat("Unexpected number of v2 trips after all lines have been flushed", trips.size(), is(7)); assertThat("Unexpected number of v2 trips after all lines have been flushed", trips.size(), is(7));
assertThat("Unexpected number of v2 trips in list 2 after all lines have been flushed", trips2.size(), is(6)); assertThat("Unexpected number of v2 trips in list 2 after all lines have been flushed", trips2.size(), is(5));
assertThat("Same object should have been pushed to both lists", trips.containsAll(trips2)); assertThat("Same object should have been pushed to both lists", trips.containsAll(trips2));
} }
@ -186,10 +163,10 @@ public class AsyncUraTripReaderTest {
Deque<Trip> trips = new ConcurrentLinkedDeque<>(); Deque<Trip> trips = new ConcurrentLinkedDeque<>();
// Start with V1 data and read file to mock list. // Start with V1 data and read file to mock list.
readLinesToMock(UraClientTest.class.getResource("stream_V1_stops_all.txt")); readLinesToMock(1, "/__files/stream_V1_stops_all.txt", 8);
AsyncUraTripReader tr = new AsyncUraTripReader( AsyncUraTripReader tr = new AsyncUraTripReader(
UraClientTest.class.getResource("stream_V1_stops_all.txt"), new URL(httpMock.baseUrl() + "/interfaces/ura/stream_V1"),
Collections.singletonList( Collections.singletonList(
trip -> { trip -> {
trips.add(trip); trips.add(trip);
@ -205,29 +182,16 @@ public class AsyncUraTripReaderTest {
TimeUnit.MILLISECONDS.sleep(100); TimeUnit.MILLISECONDS.sleep(100);
assumeTrue(trips.isEmpty(), "Trips should empty after 100ms without reading"); assumeTrue(trips.isEmpty(), "Trips should empty after 100ms without reading");
// Now write a single line to the stream pipe. // Wait for 1s for the callback to be triggered.
assumeTrue(writeNextLine(), "First line (version info) should be written"); TimeUnit.SECONDS.sleep(1);
assumeTrue(writeNextLine(), "Second line (first record) should be written");
// Wait up to 1s for the callback to be triggered.
int i = 10;
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
assumeTrue(1 == trips.size(), "Unexpected number of trips after first entry"); assumeTrue(1 == trips.size(), "Unexpected number of trips after first entry");
// Close the stream. // Close the stream.
mockOutputStream.close();
i = 10;
counter.set(0);
while (counter.get() < 1 && i-- > 0) {
TimeUnit.MILLISECONDS.sleep(100);
}
tr.close(); tr.close();
// Wait for another second.
TimeUnit.MILLISECONDS.sleep(100);
assertThat("Unexpected number of trips after all lines have been flushed", trips.size(), is(1)); assertThat("Unexpected number of trips after all lines have been flushed", trips.size(), is(1));
} }
@ -235,47 +199,34 @@ public class AsyncUraTripReaderTest {
/** /**
* Read an input file to the line buffer. * Read an input file to the line buffer.
* *
* @param url Input URL. * @param version API version.
* @throws IOException Error reading the data. * @param resourceFile Resource file name.
* @param chunks Number of chunks.
*/ */
private static void readLinesToMock(URL url) throws IOException { private void readLinesToMock(int version, String resourceFile, int chunks) {
try (InputStream is = url.openStream(); WireMock.stubFor(get(urlPathEqualTo("/interfaces/ura/stream_V" + version))
BufferedReader br = new BufferedReader(new InputStreamReader(is))) { .willReturn(aResponse()
String line = br.readLine(); .withTransformer("stream-transformer", "source", resourceFile)
while (line != null) { .withTransformer("stream-transformer", "chunks", chunks)
MOCK_LINES.add(line); )
line = br.readLine(); );
}
}
} }
/** public static class StreamTransformer extends ResponseTransformer {
* Write next line from the buffer to the mocked stream pipe. @Override
* public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
* @return {@code true} if a line has been written. int chunks = parameters.getInt("chunks", 1);
* @throws IOException Error writing the data. return Response.Builder.like(response)
*/ // Read source file to response.
private static boolean writeNextLine() throws IOException { .body(() -> AsyncUraTripReaderTest.class.getResourceAsStream(parameters.getString("source")))
String line = MOCK_LINES.poll(); // Split response in given number of chunks with 500ms delay.
if (line != null) { .chunkedDribbleDelay(new ChunkedDribbleDelay(chunks, chunks * 500))
line += "\n"; .build();
mockOutputStream.write(line.getBytes(StandardCharsets.UTF_8));
mockOutputStream.flush();
return true;
} else {
return false;
} }
}
/** @Override
* Function to mock the static {@code AsyncUraTripReader#getInputStream(URL)} method. public String getName() {
* return "stream-transformer";
* @param url URL to read from. }
* @return Input Stream.
* @throws IOException On errors.
*/
public static InputStream getInputStream(URL url) throws IOException {
mockOutputStream = new PipedOutputStream();
return new PipedInputStream(mockOutputStream);
} }
} }