When your test setup is bigger than the actual test
TL;DR: Bloated setup that’s only partially used makes your tests more coupled and harder to understand.
Problems 😔
- Coupling
- Readability
- Wasted execution time
- Misleading setup context
- Hidden test dependencies
- Harder maintenance
- Brittle test suite
- Confusing dependencies
- Slower execution
- Misleading context
Solutions 😃
- Create focused setup methods
- Apply test-specific fixtures
- Create minimal setups
- Implement test factory methods
Refactorings ⚙️
https://hackernoon.com/improving-the-code-one-line-at-a-time
https://maximilianocontieri.com/refactoring-011-replace-comments-with-tests
Context 💬
When you write tests, you might create a large setup method that initializes various objects.
If only one test uses all these objects while other tests use just a small subset, you create unnecessary overhead.
This common issue happens when you expect that future tests might need an extensive setup, or when you keep adding to an existing setup without evaluating what’s truly needed.
The tests are harder to understand since they contain irrelevant context, and slower to execute because you initialize objects that aren’t used.
Sample Code 📖
Wrong ❌
public class TVSeriesTest {
private MovieSeries theEthernaut;
private List<Character> characters;
private List<Episode> episodes;
private User user;
private UserPreferences preferences;
private RatingSystem ratingSystem;
private StreamingService streamingService;
private List<Review> reviews;
@BeforeEach
public void setUp() {
// Create a complex movie series with many characters
characters = new ArrayList<>();
characters.add(new Character("Juan Salvo", "Richard Darin"));
characters.add(new Character("Helen", "Carla Peterson"));
characters.add(new Character("Favalli", "Cesar Troncoso"));
// Create episodes
episodes = new ArrayList<>();
episodes.add(
new Episode("The Snow", 2025, 121));
episodes.add(
new Episode("The Hands Strikes Back", 2027, 124));
// Create user with preferences
preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
preferences.setSubtitlesEnabled(true);
user = new User("JohnDoe", "[email protected]", preferences);
// Create rating system with reviews
ratingSystem = new RatingSystem(10);
reviews = new ArrayList<>();
reviews.add(
new Review(user, "The Snow", 9, "Classic!"));
reviews.add(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
ratingSystem.addReviews(reviews);
// Create streaming service
streamingService = new StreamingService("Netflix");
streamingService.addMovieSeries("The Eternaut");
// Finally create the movie series with all components
theEthernaut =
new TVSeries("The Ethernaut", characters, episodes);
theEthernaut.setRatingSystem(ratingSystem);
theEthernaut.setAvailableOn(streamingService);
// This method is too long. That is another smell
}
@Test
public void testTVSeriesRecommendation() {
// This test uses almost everything from the setup
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(user, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
// You are testing the recomendation Engine
// This is not this object's responsibility
}
@Test
public void testEpisodeCount() {
// This test only needs the episodes count
assertEquals(2, theEthernaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// This test only needs the characters
// And not the rest of the setup
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Juan Salvo", juan.actor());
}
}
Right 👉
public class TVSeriesTest {
// No shared setup
@Test
public void testRecommendation() {
// Create only what's needed for this specific test
// And move this test with the behavior
TVSeries theEternaut = createTheEternautSeries();
User homer = createUserWithPreferences();
addReviewsForUser(theEternaut, homer);
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(homer, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
}
@Test
public void testEpisodeCount() {
// Only create what's needed - just the episodes
TVSeries theEternaut = new TVSeries("The Ethernaut");
theEternaut.addEpisode(
new Episode("The Snow", 2025, 121));
theEternaut.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
assertEquals(2, theEternaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// Only create what's needed - just the characters
TVSeries theEternaut = new TVSeries("The Eternaut");
theEternaut.addCharacter(
new Character("Juan Salvo", "Richard Darin"));
theEternaut.addCharacter(
new Character("Helen", "Carla Peterson"));
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Richard Darin", juan.actor());
}
// Helper methods for specific test setup needs
private TVSeries createTheEternautTVSeries() {
TVSeries series = new TVSeries("The Eternaut");
series.addEpisode(
new Episode("The Snow", 2025, 121));
series.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
return series;
}
private User createUserWithPreferences() {
UserPreferences preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
return new User("JohnDoe", "[email protected]", preferences);
}
private void addReviewsForUser(TVSeries series, User user) {
RatingSystem ratingSystem = new RatingSystem(10);
ratingSystem.addReview(
new Review(user, "The Snow", 9, "Classic!"));
ratingSystem.addReview(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
series.setRatingSystem(ratingSystem);
}
}
Detection 🔍
You can detect this smell by comparing what’s set up in the setup methods against what’s used in each test.
Look for tests that use less than 50% of the initialized objects.
Code coverage tools can help identify unused setup objects by showing which parts of the setup aren’t executed by certain tests.
If you find yourself writing conditionals in the setup to create different contexts, it’s a clear sign you need a test-specific setup instead.
Level 🔋
Why the Bijection Is Important 🗺️
Each test should reflect a specific real-world scenario.
Bloated setups break this clarity, making it hard to see what’s being tested and increasing the chance of errors.
This broken bijection makes tests harder to understand because you can’t determine which aspects of the setup are critical for the test and which are just noise.
When a test fails, you’ll spend more time investigating dependencies that might not be relevant to the failure.
The test becomes more brittle since changes to unused objects can still break tests if those objects participate in the setup process.
AI Generation 🤖
AI code generators often create this smell when they generate comprehensive test fixtures that try to cover all possible scenarios.
They prioritize completeness over focus, resulting in bloated setup methods that initialize more objects than needed for individual tests.
AI Detection 🥃
AI can detect this smell with simple instructions like “Optimize my test setup only to include what’s needed for each test.”
Modern AI tools can compare setup code against test method usage and suggest targeted refactorings, separating shared setup from test-specific setup.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Break the tests and the setup
Conclusion 🏁
Overloaded test setups that initialize objects only needed by a few tests make your test suite harder to understand and maintain.
When you create focused setups that contain only what each test needs, you improve the clarity, speed, and reliability of your tests.
Remember that tests aim to document behavior through examples and replace comments.
Too much irrelevant context makes those examples less readable. Clean tests tell a clear story without unnecessary distractions.
Relations 👩❤️💋👨
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xxv
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xi-sit35t1
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xxiii
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xli
More Information 📕
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Marcin Simonides on Unsplash
If you have to create a lot of structure before a test, maybe you’re testing through too many layers
James Shore
This article is part of the CodeSmell Series.