Skip to content

Commit 7090119

Browse files
committed
Align SessionMock.getService with real behavior
- Make getService(Class) throw NoSuchElementException for unknown services by default (matching AbstractSession) - Centralize configured services in a static CONFIGURED_SERVICES set and check via contains - Apply default-throw behavior only for non-configured services using Mockito argThat - Deprecate getMockSessionWithEnhancedServiceBehavior(...) in favor of getMockSession(...) - Update Javadoc and tests to reflect the new default behavior
1 parent a5f34da commit 7090119

File tree

4 files changed

+317
-2
lines changed

4 files changed

+317
-2
lines changed

impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionMock.java

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.NoSuchElementException;
3031
import java.util.Optional;
3132
import java.util.Properties;
3233
import java.util.concurrent.ConcurrentHashMap;
3334
import java.util.function.Supplier;
3435

36+
import java.util.Set;
3537
import org.apache.maven.api.Artifact;
3638
import org.apache.maven.api.LocalRepository;
3739
import org.apache.maven.api.ProducedArtifact;
@@ -121,6 +123,13 @@
121123
* {@link ModelXmlFactory} and {@link VersionParser}, use real implementations
122124
* to ensure correct handling of Maven models and versions.</p>
123125
*
126+
* <p><strong>Service Behavior:</strong> The {@link Session#getService(Class)} method
127+
* throws {@link NoSuchElementException} for unknown services by default, matching
128+
* the behavior of the real Session implementation. This ensures consistent behavior
129+
* between tests and production code. The implementation uses Mockito's {@code argThat}
130+
* matcher to selectively apply the default behavior only to services that are not
131+
* explicitly configured, providing a clean and maintainable approach.</p>
132+
*
124133
* @see InternalSession
125134
* @see LocalRepository
126135
* @see ProjectManager
@@ -147,7 +156,7 @@ public static InternalSession getMockSession(LocalRepository localRepository) {
147156
when(session.createRemoteRepository(anyString(), anyString())).thenAnswer(iom -> {
148157
String id = iom.getArgument(0, String.class);
149158
String url = iom.getArgument(1, String.class);
150-
return session.getService(RepositoryFactory.class).createRemote(id, url);
159+
return repositoryFactory.createRemote(id, url);
151160
});
152161
when(session.createRemoteRepository(any()))
153162
.thenAnswer(iom -> repositoryFactory.createRemote(iom.getArgument(0, Repository.class)));
@@ -452,9 +461,80 @@ public static InternalSession getMockSession(LocalRepository localRepository) {
452461
when(session.withLocalRepository(any()))
453462
.thenAnswer(iom -> getMockSession(iom.getArgument(0, LocalRepository.class)));
454463

464+
// Set up default behavior for getService to throw NoSuchElementException for unknown services
465+
// This matches the behavior of the real Session implementation
466+
// We use doAnswer with argThat to only apply to services not explicitly configured above
467+
doAnswer(invocation -> {
468+
Class<?> serviceClass = invocation.getArgument(0, Class.class);
469+
throw new NoSuchElementException(serviceClass.getName());
470+
})
471+
.when(session)
472+
.getService(ArgumentMatchers.argThat(serviceClass ->
473+
// Only throw for service classes that are NOT explicitly configured above
474+
!isConfiguredService(serviceClass)));
475+
455476
return session;
456477
}
457478

479+
/**
480+
* List of service classes explicitly configured in the mock session.
481+
*/
482+
private static final Set<Class<?>> CONFIGURED_SERVICES = Set.of(
483+
RepositoryFactory.class,
484+
VersionParser.class,
485+
LocalRepositoryManager.class,
486+
ArtifactInstaller.class,
487+
ArtifactDeployer.class,
488+
ArtifactManager.class,
489+
ProjectManager.class,
490+
ArtifactFactory.class,
491+
ProjectBuilder.class,
492+
ModelXmlFactory.class,
493+
Lookup.class);
494+
495+
/**
496+
* Checks if a service class is explicitly configured in the mock session.
497+
*/
498+
private static boolean isConfiguredService(Class<?> serviceClass) {
499+
return CONFIGURED_SERVICES.contains(serviceClass);
500+
}
501+
502+
/**
503+
* Creates a mock session with enhanced getService behavior that throws NoSuchElementException
504+
* for unknown services.
505+
*
506+
* <p><strong>Note:</strong> As of Maven 4.0.0, the regular {@link #getMockSession(String)}
507+
* method now throws NoSuchElementException by default for unknown services, matching the
508+
* behavior of the real Session implementation. This method is now equivalent to
509+
* {@link #getMockSession(String)} and is kept for backward compatibility.</p>
510+
*
511+
* @param localRepo the local repository path
512+
* @return a session that throws NoSuchElementException for unknown services
513+
* @deprecated Use {@link #getMockSession(String)} instead, which now has the same behavior
514+
*/
515+
@Deprecated
516+
public static InternalSession getMockSessionWithEnhancedServiceBehavior(String localRepo) {
517+
return getMockSession(localRepo);
518+
}
519+
520+
/**
521+
* Creates a mock session with enhanced getService behavior that throws NoSuchElementException
522+
* for unknown services.
523+
*
524+
* <p><strong>Note:</strong> As of Maven 4.0.0, the regular {@link #getMockSession(LocalRepository)}
525+
* method now throws NoSuchElementException by default for unknown services, matching the
526+
* behavior of the real Session implementation. This method is now equivalent to
527+
* {@link #getMockSession(LocalRepository)} and is kept for backward compatibility.</p>
528+
*
529+
* @param localRepository the local repository
530+
* @return a session that throws NoSuchElementException for unknown services
531+
* @deprecated Use {@link #getMockSession(LocalRepository)} instead, which now has the same behavior
532+
*/
533+
@Deprecated
534+
public static InternalSession getMockSessionWithEnhancedServiceBehavior(LocalRepository localRepository) {
535+
return getMockSession(localRepository);
536+
}
537+
458538
static String getPathForArtifact(Artifact artifact) {
459539
StringBuilder path = new StringBuilder(128);
460540
path.append(artifact.getGroupId().replace('.', '/')).append('/');

impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.HashMap;
2525
import java.util.List;
2626
import java.util.Map;
27+
import java.util.NoSuchElementException;
2728
import java.util.Optional;
2829

2930
import org.apache.maven.api.Artifact;
@@ -179,7 +180,7 @@ public Map<String, Object> getPluginContext(Project project) {
179180

180181
@Override
181182
public <T extends Service> T getService(Class<T> clazz) {
182-
return null;
183+
throw new NoSuchElementException(clazz.getName());
183184
}
184185

185186
@Override
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.plugin.testing.stubs;
20+
21+
import java.io.File;
22+
import java.util.NoSuchElementException;
23+
24+
import org.apache.maven.api.Service;
25+
import org.apache.maven.api.Session;
26+
import org.apache.maven.api.services.ArtifactFactory;
27+
import org.apache.maven.api.services.ArtifactManager;
28+
import org.apache.maven.api.services.OsService;
29+
import org.apache.maven.api.services.ProjectBuilder;
30+
import org.apache.maven.api.services.ProjectManager;
31+
import org.apache.maven.api.services.RepositoryFactory;
32+
import org.apache.maven.api.services.VersionParser;
33+
import org.apache.maven.api.services.xml.ModelXmlFactory;
34+
import org.apache.maven.impl.InternalSession;
35+
import org.junit.jupiter.api.Test;
36+
37+
import static org.junit.jupiter.api.Assertions.assertNotNull;
38+
import static org.junit.jupiter.api.Assertions.assertThrows;
39+
40+
/**
41+
* Tests for the getService method behavior in SessionMock and SessionStub.
42+
* This test verifies that both implementations correctly throw NoSuchElementException
43+
* when a service is not found, as specified in the Session interface contract.
44+
*/
45+
class SessionGetServiceTest {
46+
47+
private static final String LOCAL_REPO = System.getProperty("java.io.tmpdir") + File.separator + "test-repo";
48+
49+
/**
50+
* Test that SessionStub.getService throws NoSuchElementException for unknown services.
51+
*/
52+
@Test
53+
void testSessionStubGetServiceThrowsNoSuchElementException() {
54+
Session session = new SessionStub();
55+
56+
// Test with a service interface that should not be available
57+
NoSuchElementException exception = assertThrows(
58+
NoSuchElementException.class,
59+
() -> session.getService(OsService.class),
60+
"SessionStub.getService should throw NoSuchElementException for unknown services");
61+
62+
// Verify the exception message contains the service class name
63+
assertNotNull(exception.getMessage());
64+
assert exception.getMessage().contains(OsService.class.getName());
65+
}
66+
67+
/**
68+
* Test that SessionStub.getService throws NoSuchElementException for any service.
69+
* Since SessionStub is a minimal stub, it should not provide any services.
70+
*/
71+
@Test
72+
void testSessionStubGetServiceThrowsForAnyService() {
73+
Session session = new SessionStub();
74+
75+
// Test with various service types
76+
assertThrows(NoSuchElementException.class, () -> session.getService(ArtifactManager.class));
77+
assertThrows(NoSuchElementException.class, () -> session.getService(ProjectBuilder.class));
78+
assertThrows(NoSuchElementException.class, () -> session.getService(VersionParser.class));
79+
assertThrows(NoSuchElementException.class, () -> session.getService(RepositoryFactory.class));
80+
assertThrows(NoSuchElementException.class, () -> session.getService(ArtifactFactory.class));
81+
assertThrows(NoSuchElementException.class, () -> session.getService(ProjectManager.class));
82+
}
83+
84+
/**
85+
* Test that regular SessionMock.getService throws NoSuchElementException for unknown services.
86+
* This matches the behavior of the real Session implementation.
87+
*/
88+
@Test
89+
void testSessionMockGetServiceThrowsNoSuchElementExceptionForUnknownServices() {
90+
InternalSession session = SessionMock.getMockSession(LOCAL_REPO);
91+
92+
// Test with a service that is not configured in SessionMock
93+
NoSuchElementException exception = assertThrows(
94+
NoSuchElementException.class,
95+
() -> session.getService(OsService.class),
96+
"SessionMock.getService should throw NoSuchElementException for unknown services");
97+
98+
// Verify the exception message contains the service class name
99+
assertNotNull(exception.getMessage());
100+
assert exception.getMessage().contains(OsService.class.getName());
101+
}
102+
103+
/**
104+
* Test that enhanced SessionMock.getService throws NoSuchElementException for unknown services.
105+
* Note: This method is now equivalent to the regular getMockSession() method.
106+
*/
107+
@Test
108+
void testEnhancedSessionMockGetServiceThrowsNoSuchElementExceptionForUnknownServices() {
109+
InternalSession session = SessionMock.getMockSessionWithEnhancedServiceBehavior(LOCAL_REPO);
110+
111+
// Test with a service that is not configured in SessionMock
112+
NoSuchElementException exception = assertThrows(
113+
NoSuchElementException.class,
114+
() -> session.getService(OsService.class),
115+
"Enhanced SessionMock.getService should throw NoSuchElementException for unknown services");
116+
117+
// Verify the exception message contains the service class name
118+
assertNotNull(exception.getMessage());
119+
assert exception.getMessage().contains(OsService.class.getName());
120+
}
121+
122+
/**
123+
* Test that SessionMock.getService returns configured services successfully.
124+
*/
125+
@Test
126+
void testSessionMockGetServiceReturnsConfiguredServices() {
127+
InternalSession session = SessionMock.getMockSession(LOCAL_REPO);
128+
129+
// Test services that are configured in SessionMock
130+
assertNotNull(session.getService(RepositoryFactory.class));
131+
assertNotNull(session.getService(VersionParser.class));
132+
assertNotNull(session.getService(ArtifactManager.class));
133+
assertNotNull(session.getService(ProjectManager.class));
134+
assertNotNull(session.getService(ArtifactFactory.class));
135+
assertNotNull(session.getService(ProjectBuilder.class));
136+
assertNotNull(session.getService(ModelXmlFactory.class));
137+
}
138+
139+
/**
140+
* Test that enhanced SessionMock.getService throws NoSuchElementException for custom service interfaces.
141+
* Note: This method is now equivalent to the regular getMockSession() method.
142+
*/
143+
@Test
144+
void testEnhancedSessionMockGetServiceThrowsForCustomServices() {
145+
InternalSession session = SessionMock.getMockSessionWithEnhancedServiceBehavior(LOCAL_REPO);
146+
147+
// Test with a custom service interface
148+
assertThrows(
149+
NoSuchElementException.class,
150+
() -> session.getService(CustomTestService.class),
151+
"Enhanced SessionMock.getService should throw NoSuchElementException for custom services");
152+
}
153+
154+
/**
155+
* Custom service interface for testing purposes.
156+
*/
157+
interface CustomTestService extends Service {
158+
void doSomething();
159+
}
160+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.plugin.testing.stubs;
20+
21+
import java.io.File;
22+
import java.util.NoSuchElementException;
23+
24+
import org.apache.maven.api.Service;
25+
import org.apache.maven.api.services.ArtifactManager;
26+
import org.apache.maven.api.services.RepositoryFactory;
27+
import org.apache.maven.impl.InternalSession;
28+
import org.junit.jupiter.api.Test;
29+
30+
import static org.junit.jupiter.api.Assertions.assertNotNull;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
32+
33+
/**
34+
* Test to demonstrate the improved default behavior of SessionMock.getService().
35+
* This test shows that the mock correctly throws NoSuchElementException for unknown
36+
* services while still returning configured services.
37+
*/
38+
class SessionMockDefaultBehaviorTest {
39+
40+
private static final String LOCAL_REPO = System.getProperty("java.io.tmpdir") + File.separator + "test-repo";
41+
42+
/**
43+
* Test that demonstrates the clean default behavior:
44+
* - Configured services work correctly
45+
* - Unknown services throw NoSuchElementException by default
46+
* - No need to maintain explicit exclusion lists
47+
*/
48+
@Test
49+
void testCleanDefaultBehavior() {
50+
InternalSession session = SessionMock.getMockSession(LOCAL_REPO);
51+
52+
// Configured services should work
53+
assertNotNull(session.getService(RepositoryFactory.class));
54+
assertNotNull(session.getService(ArtifactManager.class));
55+
56+
// Unknown services should throw NoSuchElementException by default
57+
assertThrows(NoSuchElementException.class, () -> session.getService(CustomService.class));
58+
assertThrows(NoSuchElementException.class, () -> session.getService(AnotherCustomService.class));
59+
}
60+
61+
/**
62+
* Custom service interface for testing - should throw NoSuchElementException
63+
*/
64+
interface CustomService extends Service {
65+
void doSomething();
66+
}
67+
68+
/**
69+
* Another custom service interface for testing - should throw NoSuchElementException
70+
*/
71+
interface AnotherCustomService extends Service {
72+
void doSomethingElse();
73+
}
74+
}

0 commit comments

Comments
 (0)