DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Clean Unit Testing
  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case

Trending

  • Unmasking Entity-Based Data Masking: Best Practices 2025
  • AI-Based Threat Detection in Cloud Security
  • How to Practice TDD With Kotlin
  • Memory Leak Due to Time-Taking finalize() Method
  1. DZone
  2. Coding
  3. Languages
  4. Tap That Assignment With Java

Tap That Assignment With Java

Want to learn more about how to instantiate an object that will populate its state? Check out this post where we look at using built-in method, tap, to combat this problem.

By 
Joe Wolf user avatar
Joe Wolf
·
Updated Oct. 09, 18 · Tutorial
Likes (18)
Comment
Save
Tweet
Share
12.7K Views

Join the DZone community and get the full member experience.

Join For Free

When working with mutable objects, I often instantiate an object and subsequently call mutator methods to populate its state. I find this both boring and smelly.

Consider the following (admittedly contrived) unit test:

@Test
public void primary_org_is_most_precise() {
    Set<Org> orgs = new HashSet<>();
    Org org1 = new Org();
    org1.setCode("1200");
    orgs.add(org1);
    Org org2 = new Org();
    org2.setCode("1234");
    orgs.add(org2);
    Org org3 = new Org();
    org2.setCode("1230");
    orgs.add(org3);

    Employee subject = new Employee();
    subject.setOrgs(orgs);  

    Org expectedPrimaryOrg = new Org();
    expectedPrimaryOrg.setCode("1234");
    assertThat(subject.getPrimaryOrg()).isEqualTo(expectedPrimaryOrg);
}


While it's relatively straightforward, it is still hampered by smells:

  1. The method scope is polluted with unnecessary objects. The ephemeral orgs, org1, org2, and org3 variables, are only necessary for the setup of the Employee instance, which is the subject of the test.
  2. Since object instantiation and mutation are done in independent operations, I am forced to give these objects names. Furthermore, the single method scope requires me to name all objects uniquely.
  3. The number of similarly named variables sharing the sole scope puts me at risk of making a silly mistake. Such mistakes can be hard to spot. Did you notice the mistake in my example code?
  4. Overall, the test reads poorly. I must wade through a dozen lines of setup code to glean the important pieces. The ratio of setup code to assertion code seems out of balance, too.

There are ways to alleviate these issues — at least in part. I could separate the setup logic into its own dedicated method, but that introduces two methods with a 1:1 coupling per test case. The collection factory methods introduced in Java 9 could benefit, but only with the creation of the Set<Org>. I could add an Org(String code) convenience constructor and perhaps a setOrgs(Org... orgs)convenience method on Employee, but this won't scale well for more complex object graphs.

Ultimately and generally speaking, I would like to consolidate instantiation and mutation concerns whenever I assign a variable reference and have these concerns isolated in their own scope.

Is there a solution?

"Tap" to the Rescue

A number of languages offer built-in methods that offer a solution to this very problem. In Kotlin, it's called "apply;" in Ruby and Groovy, it's called "tap."

Regardless of the name, the method takes as an argument with some lambda/closure/code block that can interact with the object, the method it was invoked on, and ultimately, return that object.

Here's an example of how the test method could be re-written with Groovy's tap method:

@Test
void primary_org_is_most_precise() {
    def subject = new Employee().tap {
      orgs = new HashSet<Org>().tap {
          add(new Org().tap {
              code = '1200'
          })
          add(new Org().tap {
              code = '1234'
          })
          add(new Org().tap {
              code = '1230'
          })
      }
    }
    assert subject.primaryOrg == new Org().tap { code = '1234' }
}


Granted, there are alternatives to tap that could be used here instead, but the point is that the tap method resolves the aforementioned smells with my original code.

  1. It creates distinct scopes for each Org instance and the Set.
  2. In doing so, it eliminates the requirement to give each instance a unique name.
  3. Since the objects are not in the same scope, I avoid mistakes related to naming. There's no way for me to accidentally set the code property of the second Org instance twice, like I did in my original Java method.
  4. Because all setup logic is contained with a sub-scope of the test method's scope, I'm better able to visually distinguish setup code from assertion code.

Mimicking Tap in Java

Although not as elegant as Groovy, a tap-like method can be created in Java fairly easily:

public static <T> T tap(T object, Consumer<T> consumer) {
    consumer.accept(object);
    return object;
}


The primary differences between this Java method and Groovy's are:

  1. It's a static method versus an (extension) instance method on java.lang.Object.
  2. The Java method passes the object as an argument to the consumer, whereas the Groovy method will simply delegate to the object tap was invoked on. Because Java's Consumer needs an argument, I'm forced to name it, but at least the name is associated with its own scope.

Usage Examples

Let's look at some examples for using our new tap method. Here is the original test method re-written so that the employee assignment is tapped:

@Test
public void primary_org_is_most_precise() {
    Employee subject = tap(new Employee(), employee -> {
        employee.setOrgs(tap(new HashSet<>(), orgs -> {
            orgs.add(tap(new Org(), org -> org.setCode("1200")));
            orgs.add(tap(new Org(), org -> org.setCode("1234")));
            orgs.add(tap(new Org(), org -> org.setCode("1230")));
        }));
    });

    assertThat(subject.getPrimaryOrg()).isEqualTo(tap(new Org(), org -> org.setCode("1234")));
}


Overall, I consider this a better method than the original.

The tap method is also nice for dealing with the annoying java.util.Date API:

    Date date = tap(Calendar.getInstance(), cal -> {
        cal.set(Calendar.YEAR, 2021);
        cal.set(Calendar.DAY_OF_MONTH, 9);
        cal.set(Calendar.MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 20);
        cal.set(Calendar.MINUTE, 3);
    }).getTime();


I've also discovered tap to be useful for simply isolating concerns within a unit test method. For example, consider an integration test for adding a user via a RESTful interface. There are three REST calls to make:

  1. A GET to ensure the user does not exist before attempting to create it.
  2. The PUT to add the user.
  3. Another GET to make sure the user was added.

Each of these three HTTP requests can be done within its own tap call to isolate objects related to the response and its payload.

Java (programming language) unit test Object (computer science) Test method

Opinions expressed by DZone contributors are their own.

Related

  • Clean Unit Testing
  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!