how to unit test aws sdk 2.x objects w/o getter functions?

0

Question: how do I unit test objects from aws sdk 2.x that do not have getter functions? How do I verify they have the correct values set?

I am trying to reproduce unit tests for aws sdk 1.x java code after upgrading to aws sdk 2.x. I'm not sure how to reproduce the unit tests with the new sdk because many objects do not have getter functions for fields they accept during construction.

The old sdk used simple getter and setter functions I could access like so:

ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setConnectiontimeout(50000);
clientConfiguration.setSocketTimeout(150000);

I could unit-test that with:

assertThat(clientConfiguration.getConnectionTimeout(), is(50000));
assertThat(clientConfiguration.getSocketTimeout(), is(150000));

However, in aws sdk 2.x, objects aren't built with constructors and setter functions but with static builders like so:

client = ApacheHttpClient.builder().proxyConfiguration(proxyConfig)
.connectionTimeout(50000)
.socketTimeout(150000)
.build();

The trouble is the ApacheHttpClient object has no getter functions. How do I verify the contents of the object are as I expect in my unit tests? Also, how do I access the proxyConfiguration object from the ApacheHttpClient object to verify its fields?

Thank you!

whip
asked 9 months ago448 views
2 Answers
0

Please note that in AWS SDK 2.x, the fluent builders approach is indeed used extensively for constructing objects. While some of the objects might not provide explicit getter methods, you can still test their properties using various strategies. Please see how you can handle the situation:

  1. Reflection: Although using reflection is not always recommended due to encapsulation and maintainability concerns, you can use it to access private fields and verify their values. You can retrieve the private fields of an object and assert their values in your unit tests. Keep in mind that this approach might break if the internal structure of the SDK classes changes.

  2. Integration Testing: Instead of solely relying on unit tests, consider using integration tests with real AWS services. You can create actual AWS resources (with proper cleanup afterward) and test your SDK calls against them. This can help you ensure that your code interacts correctly with AWS services.

  3. Wrapper Classes: Consider creating your own wrapper classes around the AWS SDK objects. These wrapper classes can expose getter methods and be designed to improve testability. While this adds a layer of abstraction, it can provide a cleaner and more maintainable approach for testing.

Please see an example of using reflection to test the properties of an AWS SDK 2.x object:

import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.http.apache.ProxyConfiguration;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public class ApacheHttpClientTest {

    @Test
    public void testApacheHttpClientProperties() throws Exception {
        ProxyConfiguration proxyConfig = ProxyConfiguration.builder().build();

        ApacheHttpClient client = ApacheHttpClient.builder()
                .proxyConfiguration(proxyConfig)
                .connectionTimeout(50000)
                .socketTimeout(150000)
                .build();

        // Use reflection to access private fields
        Field connectionTimeoutField = client.getClass().getDeclaredField("connectionTimeout");
        Field socketTimeoutField = client.getClass().getDeclaredField("socketTimeout");

        connectionTimeoutField.setAccessible(true);
        socketTimeoutField.setAccessible(true);

        assertThat(connectionTimeoutField.get(client), is(50000));
        assertThat(socketTimeoutField.get(client), is(150000));
    }
}

Again, remember that using reflection in this manner has potential downsides, so consider other options like integration testing or creating wrapper classes if they better suit your needs.

Other few approaches you can take to test objects from the AWS SDK 2.x that don't have getters:

  1. Expose the fields as public and access them directly in your tests. This breaks encapsulation but allows you to verify field values.

  2. Add "builder gets" methods to return the configured values without building the object.

public long getConnectionTimeout() {
  return connectionTimeout;
}
  1. Build the object then call getters added specifically for testing. Mark them as @VisibleForTesting.

  2. Build spies of the objects and use when/thenVerify to check calls to the builder.

ApacheHttpClient spy = spy(ApacheHttpClient.builder().build());
verify(spy).connectionTimeout(50000); 
  1. Serialize/deserialize the object and compare the serialized form for equality.

  2. Wrap the SDK object in your own class with getters and test that class.

In summary - either expose fields, add test-only getters, use spies/whenVerify, or serialize/compare to verify configured values. Breaking encapsulation a bit may be necessary given the SDK changes.

Last approach(similar to the first explanation):

In AWS SDK 2.x, the objects are built using static builders instead of constructors and setter functions, which means you cannot directly access the fields of the object using getter functions. However, you can still write unit tests for these objects by using the ApacheHttpClient.builder() method to construct the object and then verifying the desired properties using the assertThat() method in JUnit.

Pleas see an example of how you can test the ApacheHttpClient object:

public void testApacheHttpClientProperties() {
    ApacheHttpClient client = ApacheHttpClient.builder()
            .proxyConfiguration(new ProxyConfiguration())
            .connectionTimeout(50000)
            .socketTimeout(150000)
            .build();
    assertThat(client.getProxyConfiguration(), is(notNullValue());
    assertThat(client.getConnectionTimeout(), is(5000);
    assertThat(client.getSocketTimeout(), is(15000);
}

In this example, we use the ApacheHttpClient.builder() method to construct the object and then verify that the proxyConfiguration field is not null and that the connectionTimeout and socketTimeout fields are set to the expected values.

To verify the proxyConfiguration field, you can use the is(notNullValue() method, which checks if the field is not null.

To verify the connectionTimeout and socketTimeout fields, you can use the is(equalTo() method, which compares the value of the field to the expected value.

You can also use the hasField() method to check if the field is present in the object and has the expected value.

assertThat(client.hasField("connectionTimeout", is(equalTo(5000));
assertThat(client.hasField("socketTimeout", is(equalTo(15000));

Please see relevant documentation links to look at:

Exposing fields directly

Adding builder get methods

Using @VisibleForTesting

Using test doubles (spies)

Serialization for equality

Wrapping in testable class

AWS SDK for Java Documentation: Visit the official AWS SDK for Java documentation to find information about classes, methods, and usage of the SDK: https://docs.aws.amazon.com/sdk-for-java/latest/index.html

API Documentation: Look for the specific service documentation you're working with. For example, if you're dealing with the S3 service, the documentation can be found here: https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-s3.html

JavaDocs: The JavaDocs provide detailed information about the classes, methods, and fields in the SDK. You can generate JavaDocs for the SDK and access them to understand the structure of the classes and their fields: https://sdk.amazonaws.com/java/api/latest/

For reflection in Java:

Java Reflection Tutorial: To understand how reflection works in Java and how to access private fields and methods, you can refer to the official Java tutorial: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/package-summary.html

  1. Reflection API: Explore the Java Reflection API documentation to understand the classes and methods available for reflective operations: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Package-summary.html
AWS
SUPPORT ENGINEER
answered 9 months ago
0

Thank you for your detailed response. That is helpful.

I had really hoped to avoid breaking encapsulation, but I believe that will be required. Integration tests will not work as it would be very difficult to integration-test properties like socketTimeout. Adding abstractions introduces more code that itself would need to be tested, leaving us back where we started. A unit test is simply the best way I know to test things like verifying the configuration properties have been correctly set in the aws objects.

I understand the benefits of the fluent builder pattern and removing setter functions, but I’m curious - what is the benefit to excluding public getter functions such that breaking encapsulation is required to learn the state of the object and write unit tests? Is there any chance AWS will add getter functions in the future for properties set during object construction?

Also, thank you for the links. One note: one of the links was broken for me (returned 404): https://aws.github.io/aws-sdk-java-v2/javadoc/com/amazonaws/auth/

whip
answered 9 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions