Skip to content

Add actual() to access the object under test #3489

Closed

Description

@goatfryed

Feature summary

I propose the additional of a unwrap method to get the actual value of the current Assert

Example

String objName = assertThat(obj).extracting(MyClass::getName).isNotNull().unwrap()

Reasoning

Ideally, in unit tests and with assertJ's fluent api, there should be little need to get the actual value back.
The use of some unwrap method might indicate bad test design, because a test should do only one thing and if you need a nested value for anything other than more assertions, your test does more than one one thing probably.

I assume this is part of the reason why it doesn't exist at the moment. No need/could promote bad practice.

Lately, I've used assertJ a lot for system integration tests on rest api level as well together with other great libraries like awaitility. I often find myself in the situation that I need to verify nested objects and then continue with them.

Compare this case

var order = orderApi.getOrder(orderId);
var troubleTicketRef = assertThat(order)
    /* ... order assertions */
    .extracting(Order::getPositions)
    .asInstanceOf(list(Position.class))
    .singleElement()
        /* ... position assertions */
        .extracting(Position::getReferences)
        .asInstanceOf(list(Reference.class))
        .filteredOn("type", "troubleTicket")
        .singleElement()
            /* ... assertions on config reference **/
            .unwrap();

var troubleTicket = troubleTicketApi.get(troubleTicketRef.getId());
assertThat(troubleTicket)
    /* ... */;

At that point I find myself to replicate a lot of the access or assertion logic, because I can't just simply get Y out of the assertion chain.

A workaround is to use satisfy to save the actual value in a reference and retrieve it by side effect, but that is ugly of course.

So for use cases on a higher level than unit tests, I think an unwrap method would be a great addition.

Sorry, if this was asked before. I searched through the repository to find something similar. I would have made a simple PR and let you decide there for this minor addition, but I wanted to understand and respect the project philosophy before I do that.

Pinned by scordio

Activity

  1. scordio commented on May 26, 2024

    @scordio
    Member

    Could you provide a real-life example you'd like to see improved?

    A similar request has been made at #1652 and #3262, but I wonder if yours is 100% a duplicate.

  2. joel-costigliola commented on May 26, 2024

    @joel-costigliola
    Member

    I want to understand a bit more the problem you are trying to solve @goatfryed.

    You are talking about asserting on X and then want to get Y out of X and make further assertions on Y.
    I would typically write something like:

    // step 1
    X x = computeX();
    assertThat(x).isNotNull();
    Name name = x.getName(); // Y is Name here
    assertThat(name).startsWith("Foo");
    // step 2
    Stuff s = computeStuff(name);
    // alternative: call x.getName() twice and remove name local variable
    assertThat(s).isNotNull(); 

    Is there something that prevents you to do that ? Maybe there is no getName() and you would need to use introspection to get it ? I don't think it is that ugly (but that's just my opinion)

    If I understand you correctly, you would prefer code like:

    X x = computeX();
    Name name = assertThat(computeX()).isNotNull()
                                      .extracting(X::getName) // or extracting("name") by reflection
                                      .startsWith("Foo")
                                      .unwrap();
    Stuff s = computeStuff(n); 
    assertThat(s).isNotNull(); 

    Note this other alternative:

    X x = computeX();
    Name name = assertThat(computeX()).isNotNull()
                                      .extracting(X::getName) // or extracting("name") by reflection
                                      .startsWith("Foo")
                                      .extracting(n -> computeStuff(n))
                                      .isNotNull(); 
  3. goatfryed commented on May 27, 2024

    @goatfryed
    Author

    I've updated the description. The initial example was indeed quite abstract.

    I find this helpful mostly in combination with deeper nested list properties. Especially in combination with thinks like filteredOn.

    It's not difficult to access the nested value for further processing, but it feels redundant to traverse the object structure once to assert and then a second time to continue processing.

    My use case is mostly in integration test calling various apis. Here, the object tree tends to get large. It's not even mostly about filtering. Some of my use cases include paths like order.items[0].product.relations[0].product.places[0].address. So even if the access chain is straightforward, it feels redundant and makes the test harder to read, because the reader needs to understand that i'm accessing the previously asserted element with my call chain.

  4. added
    status: team discussionAn issue we'd like to discuss as a team to make progress
    and removed on May 27, 2024
  5. thunderhook commented on Aug 7, 2024

    @thunderhook

    I think this is the same thing that was proposed/requested in #2098 and #1931 (comment). We also have some parts of our large testing codebase where something like this might be useful.

    While this may not be considered to be part of the API, you can use the custom extension mentioned in #1931 (comment) which would be used in your example like this:

    var order = orderApi.getOrder(orderId);
    var troubleTicketRef = assertThat(order)
        /* ... order assertions */
        .extracting(Order::getPositions)
        .asInstanceOf(list(Position.class))
        .singleElement()
            /* ... position assertions */
            .extracting(Position::getReferences)
            .asInstanceOf(list(Reference.class))
            .filteredOn("type", "troubleTicket")
            .singleElement(as(actualReturning(TroubleTicket.class))) // your actual class, var is hiding it for me...
                /* ... assertions on config reference **/
                .getActual();

    You can of course adapt the extension and rename getActual() to unwrap().

  6. joel-costigliola commented on Aug 8, 2024

    @joel-costigliola
    Member

    We are discussing with the team about exposing actual (likely through an actual() method rather than unwrap()), we'll keep you up to date when a decision has been reached.

  7. joel-costigliola commented on Aug 19, 2024

    @joel-costigliola
    Member

    Team decision: adding actual().

  8. added this to the 3.27.0 milestone on Aug 19, 2024
  9. added a commit that references this issue on Aug 23, 2024
    8d790b4
  10. pinned this issue on Sep 9, 2024
  11. scordio commented on Sep 9, 2024

    @scordio
    Member
  12. changed the title [-]Add an unwrap method to the API to get actual value[/-] [+]Add `actual()` to access the object under test[/+] on Dec 14, 2024
  13. unpinned this issue on Dec 19, 2024
  14. added a commit that references this issue on Mar 4, 2025
    5eed06f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions