service-stub/README.md
Provide a lightweight, simplified implementation of a remote or external service to facilitate testing in isolation.
Real-world example
A real-world analogy for the Service Stub pattern could be a flight simulator used to train pilots. Instead of learning to fly directly using a real airplane—which would be costly, dangerous, and often impractical—pilots initially practice within a simulator. This simulator provides predefined, realistic scenarios and reactions, enabling pilots to train safely, repeatedly, and predictably without the complexities and risks associated with actual flight operations. Similarly, a Service Stub provides controlled, predictable responses for external services during testing, simplifying and accelerating software development and testing processes.
In plain words
Use a fake service to return predictable results without relying on external systems.
Wikipedia says
A test stub is a dummy component used during testing to isolate behavior.
Sequence diagram
We demonstrate the Service Stub pattern using a simple sentiment analysis example. To illustrate this clearly, we define a common interface SentimentAnalysisServer and create two separate implementations:
RealSentimentAnalysisServer: Represents a slow, realistic sentiment analysis service, returning random sentiment results to simulate external complexity and latency.
StubSentimentAnalysisServer: Provides fast, deterministic results based on simple keyword matching, suitable for isolated testing without external dependencies.
First, define a common interface that both implementations will use:
public interface SentimentAnalysisServer {
String analyzeSentiment(String text);
}
Next, we create a realistic implementation that simulates a slow, external service. It introduces a delay of 5 seconds and returns random sentiment results (Positive, Negative, or Neutral). For flexibility and easier testing, it allows injecting a custom sentiment supplier:
public class RealSentimentAnalysisServer implements SentimentAnalysisServer {
private final Supplier<Integer> sentimentSupplier;
public RealSentimentAnalysisServer(Supplier<Integer> sentimentSupplier) {
this.sentimentSupplier = sentimentSupplier;
}
public RealSentimentAnalysisServer() {
this(() -> new Random().nextInt(3));
}
@Override
public String analyzeSentiment(String text) {
int sentiment = sentimentSupplier.get();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return sentiment == 0 ? "Positive" : sentiment == 1 ? "Negative" : "Neutral";
}
}
Then, we provide a simplified stub implementation designed specifically for testing purposes. It returns immediate and predictable results based on simple keyword detection. This enables tests to run quickly and consistently without relying on external factors:
public class StubSentimentAnalysisServer implements SentimentAnalysisServer {
@Override
public String analyzeSentiment(String text) {
if (text.toLowerCase().contains("good")) {
return "Positive";
}
else if (text.toLowerCase().contains("bad")) {
return "Negative";
}
else {
return "Neutral";
}
}
}
Finally, here's the main application logic illustrating how to use both implementations in practice. Notice the significant performance difference between the real and stub implementations:
@Slf4j
public static void main(String[] args) {
LOGGER.info("Setting up the real sentiment analysis server.");
RealSentimentAnalysisServer realSentimentAnalysisServer = new RealSentimentAnalysisServer();
String text = "This movie is soso";
LOGGER.info("Analyzing input: {}", text);
String sentiment = realSentimentAnalysisServer.analyzeSentiment(text);
LOGGER.info("The sentiment is: {}", sentiment);
LOGGER.info("Setting up the stub sentiment analysis server.");
StubSentimentAnalysisServer stubSentimentAnalysisServer = new StubSentimentAnalysisServer();
text = "This movie is so bad";
LOGGER.info("Analyzing input: {}", text);
sentiment = stubSentimentAnalysisServer.analyzeSentiment(text);
LOGGER.info("The sentiment is: {}", sentiment);
}
In summary, implementing a Service Stub involves creating a simplified substitute (StubSentimentAnalysisServer) for an actual external service (RealSentimentAnalysisServer). This approach allows your tests to run quickly and consistently by isolating them from external complexity and unpredictability.
Benefits:
Trade-offs: