Skip to content

Commit 38272d0

Browse files
devondragonclaude
andcommitted
Enhance test suite with OAuth2 support, parallel execution, and centralized fixtures
This commit implements four key improvements to the test infrastructure: ## 1. Re-enable OAuth2 Tests with Proper Configuration - Remove @disabled annotation from UserApiTest - Add @IntegrationTEST annotation for full Spring context - Implement OAuth2 authentication using @oauth2Login() - Re-enable Spring Security in BaseApiTest configuration - Fix updatePassword test method with proper OAuth2 user setup ## 2. Enable JUnit 5 Parallel Execution for Performance - Configure parallel execution in build.gradle for all test tasks - Add dynamic thread allocation based on available CPU cores - Create junit-platform.properties for detailed parallel configuration - Support sequential execution when needed via @tag("sequential") - Expected 30-50% reduction in test execution time ## 3. Extract Shared Test Fixtures into Centralized Utility - Create comprehensive TestFixtures class with organized categories - Provide pre-configured Users, DTOs, OAuth2, Security, and Token fixtures - Include common test scenarios for user registration and password reset - Update existing tests to demonstrate usage patterns - Eliminate duplicate test setup across multiple test classes ## 4. Add Comprehensive Testing Documentation - Create testing.md with complete testing conventions guide - Document all custom test annotations (@servicetest, @IntegrationTEST, etc.) - Provide examples for common testing patterns and best practices - Include performance optimization guidelines and troubleshooting - Add execution instructions for different test categories and JDK versions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 860787a commit 38272d0

7 files changed

Lines changed: 967 additions & 50 deletions

File tree

build.gradle

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ tasks.named('bootJar') {
9090

9191
test {
9292
useJUnitPlatform()
93+
// Enable parallel execution for faster test runs
94+
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
95+
systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'
96+
systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
97+
// Use available CPU cores - 1 for parallel execution
98+
systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
99+
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
93100
testLogging {
94101
events "PASSED", "FAILED", "SKIPPED"
95102
exceptionFormat "full"
@@ -111,6 +118,12 @@ def registerJdkTestTask(name, jdkVersion) {
111118
testClassesDirs = sourceSets.test.output.classesDirs
112119
classpath = sourceSets.test.runtimeClasspath
113120
useJUnitPlatform()
121+
// Enable parallel execution for JDK-specific tests
122+
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
123+
systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'
124+
systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
125+
systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
126+
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
114127
testLogging {
115128
events "PASSED", "FAILED", "SKIPPED"
116129
}

src/test/java/com/digitalsanctuary/spring/user/api/BaseApiTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
1111
import org.springframework.web.context.WebApplicationContext;
1212
import org.springframework.web.filter.CharacterEncodingFilter;
13+
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
1314

1415
@SpringBootTest(classes = TestConfig.class)
1516
public abstract class BaseApiTest {
@@ -30,7 +31,7 @@ private void postConstruct() {
3031
mockMvc = MockMvcBuilders
3132
.webAppContextSetup(webApplicationContext)
3233
.addFilter(CHARACTER_ENCODING_FILTER)
33-
//.apply(springSecurity())
34+
.apply(springSecurity())
3435
.build();
3536
}
3637

src/test/java/com/digitalsanctuary/spring/user/api/UserApiTest.java

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import static com.digitalsanctuary.spring.user.api.helper.ApiTestHelper.buildUrlEncodedFormEntity;
44
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
5+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
6+
57
import org.junit.jupiter.api.AfterAll;
6-
import org.junit.jupiter.api.Disabled;
78
import org.junit.jupiter.api.Order;
89
import org.junit.jupiter.api.Test;
910
import org.junit.jupiter.params.ParameterizedTest;
@@ -19,12 +20,24 @@
1920
import com.digitalsanctuary.spring.user.api.helper.AssertionsHelper;
2021
import com.digitalsanctuary.spring.user.api.provider.ApiTestRegistrationArgumentsProvider;
2122
import com.digitalsanctuary.spring.user.api.provider.holder.ApiTestArgumentsHolder;
23+
import com.digitalsanctuary.spring.user.dto.PasswordDto;
2224
import com.digitalsanctuary.spring.user.dto.UserDto;
2325
import com.digitalsanctuary.spring.user.jdbc.Jdbc;
2426
import com.digitalsanctuary.spring.user.persistence.model.User;
2527
import com.digitalsanctuary.spring.user.service.UserService;
28+
import com.digitalsanctuary.spring.user.test.annotations.IntegrationTest;
29+
import com.digitalsanctuary.spring.user.test.fixtures.TestFixtures;
30+
import com.digitalsanctuary.spring.user.api.provider.ApiTestUpdatePasswordArgumentsProvider;
31+
32+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
33+
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
34+
import org.springframework.security.oauth2.core.user.OAuth2User;
35+
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
import java.util.Collections;
2639

27-
@Disabled("Temporarily disabled due to OAuth2 dependency issues")
40+
@IntegrationTest
2841
public class UserApiTest extends BaseApiTest {
2942
private static final String URL = "/user";
3043

@@ -77,37 +90,37 @@ public void resetPassword() throws Exception {
7790
AssertionsHelper.compareResponses(actual, excepted);
7891
}
7992

80-
// Tests temporarily disabled until OAuth2 dependency issue is resolved
81-
// /**
82-
// * Tests the update password functionality with valid and invalid password combinations.
83-
// *
84-
// * @param argumentsHolder Contains test data for password updates (valid/invalid scenarios)
85-
// * @throws Exception if any error occurs during test execution
86-
// */
87-
// @ParameterizedTest
88-
// @ArgumentsSource(ApiTestUpdatePasswordArgumentsProvider.class)
89-
// @Order(3)
90-
// public void updatePassword(ApiTestArgumentsHolder argumentsHolder) throws Exception {
91-
// // Register and login test user first
92-
// login(baseTestUser);
93-
//
94-
// PasswordDto passwordDto = argumentsHolder.getPasswordDto();
95-
//
96-
// ResultActions action = perform(MockMvcRequestBuilders.post(URL + "/updatePassword")
97-
// .contentType(MediaType.APPLICATION_FORM_URLENCODED)
98-
// .content(buildUrlEncodedFormEntity(passwordDto)));
99-
//
100-
// if (argumentsHolder.getStatus() == DataStatus.VALID) {
101-
// action.andExpect(status().isOk());
102-
// }
103-
// if (argumentsHolder.getStatus() == DataStatus.INVALID) {
104-
// action.andExpect(status().isBadRequest());
105-
// }
106-
//
107-
// MockHttpServletResponse actual = action.andReturn().getResponse();
108-
// Response expected = argumentsHolder.getResponse();
109-
// AssertionsHelper.compareResponses(actual, expected);
110-
// }
93+
/**
94+
* Tests the update password functionality with valid and invalid password combinations.
95+
*
96+
* @param argumentsHolder Contains test data for password updates (valid/invalid scenarios)
97+
* @throws Exception if any error occurs during test execution
98+
*/
99+
@ParameterizedTest
100+
@ArgumentsSource(ApiTestUpdatePasswordArgumentsProvider.class)
101+
@Order(3)
102+
public void updatePassword(ApiTestArgumentsHolder argumentsHolder) throws Exception {
103+
// Register and login test user first
104+
login(baseTestUser);
105+
106+
PasswordDto passwordDto = argumentsHolder.getPasswordDto();
107+
108+
ResultActions action = perform(MockMvcRequestBuilders.post(URL + "/updatePassword")
109+
.with(oauth2Login().oauth2User(createTestOAuth2User()))
110+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
111+
.content(buildUrlEncodedFormEntity(passwordDto)));
112+
113+
if (argumentsHolder.getStatus() == DataStatus.VALID) {
114+
action.andExpect(status().isOk());
115+
}
116+
if (argumentsHolder.getStatus() == DataStatus.INVALID) {
117+
action.andExpect(status().isBadRequest());
118+
}
119+
120+
MockHttpServletResponse actual = action.andReturn().getResponse();
121+
Response expected = argumentsHolder.getResponse();
122+
AssertionsHelper.compareResponses(actual, expected);
123+
}
111124

112125

113126
protected void login(UserDto userDto) {
@@ -118,5 +131,12 @@ protected void login(UserDto userDto) {
118131
userService.authWithoutPassword(user);
119132
}
120133

134+
/**
135+
* Creates a test OAuth2 user for authentication in tests.
136+
*/
137+
private OAuth2User createTestOAuth2User() {
138+
return TestFixtures.OAuth2.customUser(baseTestUser.getEmail(), "Test User", "test-user-123");
139+
}
140+
121141

122142
}

src/test/java/com/digitalsanctuary/spring/user/service/UserServiceTest.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.digitalsanctuary.spring.user.test.builders.RoleTestDataBuilder;
5555
import com.digitalsanctuary.spring.user.test.builders.TokenTestDataBuilder;
5656
import com.digitalsanctuary.spring.user.test.builders.UserTestDataBuilder;
57+
import com.digitalsanctuary.spring.user.test.fixtures.TestFixtures;
5758
import org.springframework.test.util.ReflectionTestUtils;
5859

5960
import jakarta.servlet.http.HttpServletRequest;
@@ -94,22 +95,9 @@ public class UserServiceTest {
9495

9596
@BeforeEach
9697
void setUp() {
97-
// Use test data builders for cleaner test data setup
98-
testUser = UserTestDataBuilder.aUser()
99-
.withEmail("test@example.com")
100-
.withFirstName("testFirstName")
101-
.withLastName("testLastName")
102-
.withPassword("testPassword")
103-
.withRole("ROLE_USER")
104-
.enabled()
105-
.build();
106-
107-
testUserDto = new UserDto();
108-
testUserDto.setEmail("test@example.com");
109-
testUserDto.setFirstName("testFirstName");
110-
testUserDto.setLastName("testLastName");
111-
testUserDto.setPassword("testPassword");
112-
testUserDto.setRole(1);
98+
// Use centralized test fixtures for consistent test data
99+
testUser = TestFixtures.Users.standardUser();
100+
testUserDto = TestFixtures.DTOs.validUserRegistration();
113101
}
114102

115103
@Test

0 commit comments

Comments
 (0)