Unit testing
Many SDK classes interact with PingFederate through the com.pingidentity.sdk.internal.services.ServiceFactory object, which looks up an implementation of some internal service at runtime using the JDK’s ServiceLoader.
Your plugin won’t usually call ServiceFactory directly. Instead, it will call the TrustedCAAccessor helper in the com.pingidentity.access package, which looks like an ordinary method call but actually depends on the server to supply a real implementation underneath.
For testing purposes there’s no real implementation for ServiceFactory to find outside of a running server. It’s also not practical to mock the functions of ServiceFactory.
The way around this is to write your own implementation of the service interface and register it as an alternate ServiceLoader provider. ServiceFactory will find it automatically when your test is running, just as it would the real provider in production.
Steps
-
Write a test implementation of the service interface.
package com.pingidentity.clientregistration; import com.pingidentity.sdk.internal.interfaces.CertWrapper; import com.pingidentity.sdk.internal.services.interfaces.TrustedCAAccessorService; public class TestTrustedCAAccessorService implements TrustedCAAccessorService { @Override public Set<TrustAnchor> getAllTrustAnchors() { Set<TrustAnchor> trustAnchors = new HashSet<>(); trustAnchors.add(new TrustAnchor(getTestCertificate(), null)); return trustAnchors; } @Override public Set<CertWrapper> getTrustedCAs() { return Collections.emptySet(); } } -
To register the test implementation for
ServiceLoader, add a file to the plugin folder undersrc/test/resources/META-INF/services/named after the fully-qualified interface it implements.src/test/resources/META-INF/services/com.pingidentity.sdk.internal.services.interfaces.TrustedCAAccessorServiceThe contents of the file are just the fully-qualified name of your test class.
com.pingidentity.clientregistration.TestTrustedCAAccessorService -
Call the accessor as normal in your test.
ServiceLoaderonly detects what’s on the classpath that it’s run with, so during a test run it finds your test implementation instead of a production implementation:@Test public void getTrustedCertificatesReturnsTestServiceCertificate() { SoftwareStatementValidatorPlugin plugin = new SoftwareStatementValidatorPlugin(); Collection<X509Certificate> trustedCertificates = plugin.getTrustedCertificates(); assertEquals(1, trustedCertificates.size()); }Since
src/test/resourcesnever gets packaged into the plugin jar, this substitution only works for testing purposes. Real deployments still resolve the genuine PingFederate-provided service.
Example
You can test this pattern using the pf-server/sdk/examples/dynamic-client-registration/ example. Its plugin, SoftwareStatementValidatorPlugin, fetches a JWKS URI over HTTPS to verify a signature, and needs a trust store to do that. This trust store is built from TrustedAccessor.getAllTrustAnchors(), the server’s imported certificate authority and self-signed certificates. That lookup is located in a small package-private method called getTrustedCertificates(), specifically so a test can call it directly.
The following table describes the test setup:
| Container | Description |
|---|---|
|
The test implementation of |
|
The |
|
The JUnit test. Calling |
From the example plugin’s directory, run mvn test.