PingFederate Server

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

  1. 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();
        }
    }
  2. To register the test implementation for ServiceLoader, add a file to the plugin folder under src/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.TrustedCAAccessorService

    The contents of the file are just the fully-qualified name of your test class.

    com.pingidentity.clientregistration.TestTrustedCAAccessorService
  3. Call the accessor as normal in your test.

    ServiceLoader only 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/resources never 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

src/test/java/com/pingidentity/clientregistration/TestTrustedCAAccessorService.java

The test implementation of TrustedCAAccessorService, backed by a fixed self-signed certificate

src/test/resources/META-INF/services/com.pingidentity.sdk.internal.services.interfaces.TrustedCAAccessorService

The ServiceLoader registration file

src/test/java/com/pingidentity/clientregistration/SoftwareStatementValidatorPluginTest.java

The JUnit test. Calling getTrustedCertificates() and checking it returns the test certificate

From the example plugin’s directory, run mvn test.