Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
<module>table-inheritance</module>
<module>bloc</module>
<module>map-reduce</module>
<module>service-stub</module>
</modules>
<repositories>
<repository>
Expand Down
153 changes: 153 additions & 0 deletions service-stub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
title: "Service Stub Pattern in Java: Simplifying Testing with Stub Implementations"
shortTitle: Service Stub
description: "Explore the Service Stub design pattern in Java using a Sentiment Analysis example. Learn how stub implementations provide dummy services to facilitate testing and development."
category: Structural
language: en
tag:
- Testing
- Decoupling
- Dummy Services
- Dependency Injection
---

## Also known as

* Dummy Service
* Fake Service

## Intent of Service Stub Pattern

The Service Stub pattern provides a lightweight, dummy implementation of an external service to allow testing or development without relying on the real service, which may be unavailable, slow, or resource-intensive.

## Detailed Explanation of Service Stub Pattern with Real-World Example

Real-world example

> In this example, we simulate a **Sentiment Analysis Service**. The real implementation delays execution and randomly decides the sentiment. The stub implementation, on the other hand, quickly returns predefined responses based on input text ("good", "bad", or neutral), making it ideal for testing.

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.

## Programmatic Example of Service Stub Pattern in Java

We define a `SentimentAnalysisService` interface and provide two implementations:

1. **RealSentimentAnalysisServer**: Simulates a slow, random sentiment analysis system.
2. **StubSentimentAnalysisServer**: Returns a deterministic result based on input keywords.

### Example Implementation
Both the real service and the stub implement the interface below.
```java
public interface SentimentAnalysisServer {
String analyzeSentiment(String text);
}
```
The real sentiment analysis class returns a random response for a given input and simulates the runtime by sleeping
the Thread for 5 seconds. The Supplier<Integer> allows injecting controlled sentiment values during testing, ensuring
deterministic outputs.
```java
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";
}
}
```
The stub implementation simulates the real sentiment analysis class and provides a deterministic output
for a given input. Additionally, its runtime is almost zero.
```java
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";
}
}
}

```
Here is the main function of the App class (entry point to the program)
```java
@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);
}
```
## When to Use the Service Stub Pattern in Java

Use the Service Stub pattern when:

* Testing components that depend on external services.
* The real service is slow, unreliable, or unavailable.
* You need predictable, predefined responses.
* Developing offline without real service access.

## Real-World Applications of Service Stub Pattern in Java

* Simulating APIs (payments, recommendation systems) during testing.
* Bypassing external AI/ML models in tests.
* Simplifying integration testing.

## Benefits and Trade-offs of Service Stub Pattern

Benefits:

* Reduces dependencies.
* Provides predictable behavior.
* Speeds up testing.

Trade-offs:

* Requires maintaining stub logic.
* May not fully represent real service behavior.

## Related Java Design Patterns

* [Proxy](https://java-design-patterns.com/patterns/proxy/)
* [Strategy](https://java-design-patterns.com/patterns/strategy/)

## References and Credits

* [Martin Fowler: Test Stubs](https://martinfowler.com/articles/mocksArentStubs.html)
27 changes: 27 additions & 0 deletions service-stub/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<artifactId>service-stub</artifactId>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
45 changes: 45 additions & 0 deletions service-stub/src/main/java/com/iluwatar/servicestub/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.iluwatar.servicestub;

import lombok.extern.slf4j.Slf4j;

/**
* A Service Stub is a dummy implementation of an external service used during development or
* testing. The purpose is to provide a lightweight "stub" when the real service may not always be
* available (or too slow to use during testing).
*
* <p>This implementation simulates a simple sentiment analysis program, where a text is analyzed to
* deduce whether it is a positive, negative or neutral sentiment. The stub returns a response based
* on whether the analyzed text contains the words "good" or "bad", not accounting for stopwords or
* the underlying semantic of the text.
*
* <p>The "real" sentiment analysis class simulates the processing time for the request by pausing
* the execution of the thread for 5 seconds. In the stub sentiment analysis class the response is
* immediate. In addition, the stub returns a deterministic output with regard to the input. This
* is extra useful for testing purposes.
*/


@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args
*/
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);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.iluwatar.servicestub;

import java.util.Random;
import java.util.function.Supplier;

/**
* Real implementation of SentimentAnalysisServer.
* Simulates random sentiment classification with processing delay.
*/

public class RealSentimentAnalysisServer implements SentimentAnalysisServer {
/**
* A real sentiment analysis implementation would analyze the input string using, e.g., NLP and
* determine whether the sentiment is positive, negative or neutral. Here we simply choose a random
* number to simulate this. The "model" may take some time to process the input and we simulate
* this by delaying the execution 5 seconds.
*
* @param text the input string to analyze
* @return sentiment classification result (Positive, Negative, or Neutral)
*/

private final Supplier<Integer> sentimentSupplier;

// Constructor
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";
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.iluwatar.servicestub;

/**
* Sentiment analysis server interface to be implemented by sentiment analysis services.
*/

public interface SentimentAnalysisServer {
/**
* Analyzes the sentiment of the input text and returns the result.
*
* @param text the input text to analyze
* @return sentiment classification result
*/
String analyzeSentiment(String text);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.iluwatar.servicestub;

/**
* Stub implementation of SentimentAnalysisServer.
* Returns deterministic sentiment based on input keywords.
*/

public class StubSentimentAnalysisServer implements SentimentAnalysisServer {

/**
* Fake sentiment analyzer, always returns "Positive" if input string contains the word "good",
* "Negative" if the string contains "bad" and "Neutral" otherwise.
*
* @param text the input string to analyze
* @return sentiment classification result (Positive, Negative, or Neutral)
*/
@Override
public String analyzeSentiment(String text) {
if (text.toLowerCase().contains("good")) {
return "Positive";
} else if (text.toLowerCase().contains("bad")) {
return "Negative";
} else {
return "Neutral";
}
}
}
11 changes: 11 additions & 0 deletions service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.iluwatar.servicestub;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

public class AppTest {
@Test
void shouldExecuteWithoutException() {
assertDoesNotThrow(() -> App.main(new String[] {}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.iluwatar.servicestub;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class RealSentimentAnalysisServerTest {

@Test
void testPositiveSentiment() {
RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 0);
assertEquals("Positive", server.analyzeSentiment("Test"));
}

@Test
void testNegativeSentiment() {
RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 1);
assertEquals("Negative", server.analyzeSentiment("Test"));
}

@Test
void testNeutralSentiment() {
RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 2);
assertEquals("Neutral", server.analyzeSentiment("Test"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.iluwatar.servicestub;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class StubSentimentAnalysisServerTest {

private final StubSentimentAnalysisServer stub = new StubSentimentAnalysisServer();

@Test
void testPositiveSentiment() {
String result = stub.analyzeSentiment("This is a good product");
assertEquals("Positive", result);
}

@Test
void testNegativeSentiment() {
String result = stub.analyzeSentiment("This is a bad product");
assertEquals("Negative", result);
}

@Test
void testNeutralSentiment() {
String result = stub.analyzeSentiment("This product is average");
assertEquals("Neutral", result);
}
}
Loading