The test suite uses JUnit 4 with JUnit 5 Vintage Engine for backward compatibility. Tests focus on unit testing individual components with extensive use of mocking for external dependencies like HTTP servers.
src/test/java/com/snowplowanalytics/snowplow/tracker/
├── TrackerTest.java # Core tracker functionality
├── SnowplowTest.java # Factory and registry
├── SubjectTest.java # Subject data management
├── UtilsTest.java # Utility functions
├── emitter/ # Emitter layer tests
├── events/ # Event type tests
├── payload/ # Payload serialization tests
└── http/ # HTTP client tests
Essential for testing tracker behavior without network calls:
// ✅ Standard MockEmitter
public static class MockEmitter implements Emitter {
public List<TrackerPayload> eventList = new ArrayList<>();
@Override
public boolean add(TrackerPayload payload) {
eventList.add(payload);
return true;
}
}Consistent test initialization:
// ✅ Standard setUp
@Before
public void setUp() {
mockEmitter = new MockEmitter();
TrackerConfiguration config = new TrackerConfiguration("AF003", "cloudfront")
.base64Encoded(false);
tracker = new Tracker(config, mockEmitter);
}For testing HTTP interactions:
// ✅ HTTP testing setup
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody("ok"));
String url = server.url("/").toString();Verify event payloads correctly:
// ✅ Payload verification
TrackerPayload payload = mockEmitter.eventList.get(0);
Map<String, String> map = payload.getMap();
assertEquals("pv", map.get("e")); // Event type
assertEquals(url, map.get("url")); // Page URL
assertNotNull(map.get("eid")); // Event ID// ✅ Test required fields
@Test(expected = NullPointerException.class)
public void testMissingRequiredField() {
PageView.builder().build(); // Missing pageUrl
}// ✅ Test optional fields
@Test
public void testOptionalFields() {
PageView event = PageView.builder()
.pageUrl("https://example.com")
.pageTitle(null) // Should work
.build();
assertNotNull(event);
}// ✅ Test custom contexts
@Test
public void testCustomContext() {
List<SelfDescribingJson> contexts = singletonList(
new SelfDescribingJson("schema",
Collections.singletonMap("key", "value"))
);
PageView event = PageView.builder()
.pageUrl("https://example.com")
.customContext(contexts)
.build();
}// ✅ Test batching behavior
@Test
public void testBatchSize() throws InterruptedException {
BatchEmitter emitter = new BatchEmitter(
networkConfig,
new EmitterConfiguration().batchSize(2)
);
emitter.add(payload1);
emitter.add(payload2); // Should trigger send
Thread.sleep(500);
// Verify batch sent
}// ✅ Test retry on failure
@Test
public void testRetryLogic() {
server.enqueue(new MockResponse().setResponseCode(500));
server.enqueue(new MockResponse().setResponseCode(200));
// Add event and verify retry
}// ✅ Test callbacks
@Test
public void testSuccessCallback() {
AtomicBoolean called = new AtomicBoolean(false);
EmitterCallback callback = new EmitterCallback() {
@Override
public void onSuccess(List<TrackerPayload> payloads) {
called.set(true);
}
};
// Verify callback invoked
}// ✅ Test JSON serialization
@Test
public void testJsonSerialization() {
SelfDescribingJson json = new SelfDescribingJson(
"schema",
Collections.singletonMap("key", "value")
);
String result = json.toString();
assertTrue(result.contains("\"schema\":\"schema\""));
}// ✅ Test encoding
@Test
public void testBase64Encoding() {
TrackerConfiguration config = new TrackerConfiguration("ns", "app")
.base64Encoded(true);
// Verify contexts are base64 encoded
}// ✅ Test payload size
@Test
public void testPayloadSize() {
TrackerPayload payload = new TrackerPayload();
payload.add("key", "value");
assertTrue(payload.getByteSize() > 0);
}// ✅ Test subject override
@Test
public void testSubjectOverride() {
Subject trackerSubject = new Subject();
trackerSubject.setUserId("tracker-user");
Subject eventSubject = new Subject();
eventSubject.setUserId("event-user");
// Event subject should override
}// ✅ Test platform setting
@Test
public void testPlatformDetection() {
Subject subject = new Subject();
subject.setPlatform(DevicePlatform.Mobile);
assertEquals("mob", subject.getSubject().get("p"));
}// ✅ Test thread safety
@Test
public void testConcurrentAccess() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
tracker.track(event);
latch.countDown();
}).start();
}
latch.await();
// Verify all events tracked
}// ✅ Test buffer limits
@Test
public void testBufferOverflow() {
EmitterConfiguration config = new EmitterConfiguration()
.bufferCapacity(2);
// Add 3 events, verify one dropped
}// ✅ Validate UUID format
private boolean isValidUUID(String id) {
try {
UUID.fromString(id);
return true;
} catch (Exception e) {
return false;
}
}// ✅ Validate timestamp
private boolean isValidTimestamp(String ts) {
try {
long timestamp = Long.parseLong(ts);
return timestamp > 0;
} catch (Exception e) {
return false;
}
}// ✅ Validate JSON structure
private boolean isValidJson(String json) {
try {
new ObjectMapper().readTree(json);
return true;
} catch (Exception e) {
return false;
}
}// ❌ Don't use real endpoints
BatchEmitter emitter = new BatchEmitter(
new NetworkConfiguration("https://real-collector.com"),
config
);
// ✅ Use MockWebServer
MockWebServer server = new MockWebServer();// ❌ Arbitrary sleep
Thread.sleep(5000); // Why?
// ✅ Sleep with purpose
Thread.sleep(100); // Allow async operation// ❌ Resources not cleaned
MockWebServer server = new MockWebServer();
// Never shutdown
// ✅ Proper cleanup
@After
public void tearDown() throws IOException {
server.shutdown();
}// ❌ Complex mock setup
when(mock.method1()).thenReturn(x);
when(mock.method2()).thenReturn(y);
// 20 more lines...
// ✅ Simple test double
class SimpleEmitter implements Emitter {
// Minimal implementation
}# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests TrackerTest
# Run with coverage
./gradlew test jacocoTestReport- Unit Tests: Individual component testing
- Integration Tests: Component interaction
- Concurrency Tests: Thread safety verification
- Performance Tests: Not in main suite
- Test Naming: Use descriptive names (testPageViewWithAllFields)
- One Assertion Per Test: Keep tests focused
- Mock External Dependencies: Never make real network calls
- Test Edge Cases: Null values, empty strings, limits
- Document Complex Tests: Add comments for non-obvious logic
- Clean Up Resources: Always close/shutdown in @After