Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssertJ string assertions not working with Spock 2.3 and Groovy #3381

Open
nweiser94 opened this issue Feb 27, 2024 · 15 comments
Open

AssertJ string assertions not working with Spock 2.3 and Groovy #3381

nweiser94 opened this issue Feb 27, 2024 · 15 comments
Assignees
Labels
3rd-party: Spock An issue related to using AssertJ with Spock language: Groovy An issue related to using AssertJ with Groovy theme: documentation An issue related to documentation
Milestone

Comments

@nweiser94
Copy link

Describe the bug
The feature from #2520 seems to be incompatible with using the StringAssertfrom assertj-core in a Groovy and Spock setup.

The then block in a spock test feature wraps any assertion code line with an groovy powerassert assert under the hood expecting the actual assertion expression to be evaluated as a boolean value. In Groovy there is the org.codehaus.groovy.runtime.DefaultGroovyMethods#asBoolean(java.lang.Object) method that Spock is calling. Due to the changes made to the AbstractStringAssert class this method is inaccessible and returns a BooleanAssert instead.

  • assertj core version: 3.25.3
  • java version: 17
  • groovy version: 4.0.18
  • test framework version: Spock 2.3-groovy-4.0
  • os (if relevant):

Test case reproducing the bug

Add a test case showing the bug that we can run

import spock.lang.Specification

import static org.assertj.core.api.Assertions.assertThat

class DemoSpec extends Specification {
    def 'assert some integer'() {
        when:
        def someInteger = 10

        then:
        assertThat(someInteger).isEqualTo(10)
    }

    def 'assert some string'() {
        when:
        def someString = 'some string'

        then:
        assertThat(someString).isEqualTo('some string')
    }
}
@joel-costigliola joel-costigliola added the language: Groovy An issue related to using AssertJ with Groovy label Feb 27, 2024
@scordio
Copy link
Member

scordio commented Feb 27, 2024

If I understand correctly, AssertJ breaks Spock interoperability as soon as the then block has string assertions like in the second example but it works correctly in the first example, right?

Is there anything in the Spock documentation about this? Mainly to understand if there are additional caveats to consider.

@scordio scordio added the 3rd-party: Spock An issue related to using AssertJ with Spock label Feb 27, 2024
@quaff
Copy link
Contributor

quaff commented Feb 28, 2024

I think it should be fixed at Spock side, it shouldn't wrap assert if statement.asBoolean() doesn't returns boolean.

@scordio
Copy link
Member

scordio commented Feb 28, 2024

I also have the feeling preventing specific method names in AssertJ shouldn't be the way to go but I'm happy to elaborate on it better.

@nweiser94 would you mind raising a related issue to Spock and see what they think? Happy to chime in, if needed.

@nweiser94
Copy link
Author

I actually think that this is not just a Spock specific issue, but a more general conflict with the Groovy language as assertj now hides Groovy built-ins.

@scordio Nonetheless, i will raise an issue to Spock to see what they think. 👍

@nweiser94
Copy link
Author

fyi: Raised Spock issue spockframework/spock#1897

@scordio
Copy link
Member

scordio commented Feb 28, 2024

I actually think that this is not just a Spock specific issue, but a more general conflict with the Groovy language as assertj now hides Groovy built-ins.

I'm reading about power assertions as a Groovy language feature. However, in your example, there is no assert keyword but, as you already mentioned:

The then block in a spock test feature wraps any assertion code line with an groovy powerassert assert under the hood

So it sounds like something where Spock would have control?

Looking forward to the maintainers' feedback.

@scordio
Copy link
Member

scordio commented Feb 29, 2024

According to spockframework/spock#1897 (comment), this isn't something to be solved in AssertJ, so I'm closing the issue but happy to reconsider in case of new arguments.

@scordio scordio closed this as not planned Won't fix, can't repro, duplicate, stale Feb 29, 2024
@scordio scordio added the status: invalid An issue that we don't feel is valid label Feb 29, 2024
@AndreasTu
Copy link

AndreasTu commented Feb 29, 2024

I am really not sure here, because this also break, when Spock is not involved, but only Groovy:

import static org.assertj.core.api.Assertions.assertThat

def someString = 'some string'
assert assertThat(someString).isEqualTo('some string')

See groovy web console

AssertJ does not play well here with the Groovy semantic to use the method asBoolean() to convert it to a boolean.

@scordio Can you have a second look?

@scordio scordio reopened this Feb 29, 2024
@scordio scordio removed the status: invalid An issue that we don't feel is valid label Feb 29, 2024
@scordio
Copy link
Member

scordio commented Feb 29, 2024

AssertJ does not play well here with the Groovy semantic

@AndreasTu is the Groovy semantic and the asBoolean as a reserved name documented somewhere?

The power assertions section doesn't seem to tell the full story. I would like to understand the overall impact, if it's really about asBoolean only or something more.

It seems quite an aggressive move to reserve a method name and force libraries not to use it... or, as you said in spockframework/spock#1897 (comment), maybe using AssertJ Core on top of Groovy power assertions makes little sense?

@AndreasTu
Copy link

@scordio You can find the documentation about that in the section Customizing the truth with asBoolean() methods in the Groovy documentation.

Groovy has more such methods, see operator overloading
It tries to map operators and other convertions to methods.
E.g. the + operator will map to add(), the [index] operator maps to getAt() etc.
My guess is that the general logic to that is common method names like add() in Numbers or BigDecimal BigInteger map to the + operator to make the usage more seamless.

@AndreasTu
Copy link

It seems quite an aggressive move to reserve a method name and force libraries not to use it.

I agree with that to a degree and Groovy should probably check the return type of asBoolean() better or apply the Groovy Truth recursive to the returned types, but this could also break, when there is a endless recursion with that.

But in general to map such semantics on top of some special methods in Groovy has more benefit than harm.
Because most of the time classes written only with Java in mind will work maybe 90% of the time with enhanced groovy semantics without any adaptions needed. So Groovy users get the benefit of the Java-Ecosystem with enhanced semantics.
Think of a class having a asBoolean() returning boolean or a add method , you can write stuff like that:

if(myObject){
 result += myObject
}

//instead of in Java
if(myObject.asBoolean()){
  result = result.add(myObject)
}

maybe using AssertJ Core on top of Groovy power assertions makes little sense?

My opinion would be: In general use AssertJ in Java code and use groovy power assertions in Groovy code.
The Groovy power assertions are IMHO more readable, but will only work in Groovy.

@scordio scordio removed the 3rd-party: Spock An issue related to using AssertJ with Spock label Feb 29, 2024
@scordio
Copy link
Member

scordio commented Feb 29, 2024

@AndreasTu looking at the plain Groovy example, what is the purpose of prefixing the assertThat call with assert?

We have some basic Groovy integration tests and none of them uses assert in front of the assertions.

In addition, the junit5-jupiter-starter-gradle-groovy from the JUnit samples doesn't use assert in front of assertions either.

My current impression is that there is nothing wrong with Groovy itself but only the way AssertJ is used with the Groovy language features.

I understand that Spock introduces a layer of abstraction on top of Groovy, "wiring" the content of the then block with the Groovy assert. I'm wondering if there is anything that could be done inside Spock to handle such a combination.

@quaff
Copy link
Contributor

quaff commented Mar 1, 2024

assert assertThat(someString).isEqualTo('some string')

It happens to work before assertj add asBoolean method, now the ClassCastException is helpful to find out such weird code which should be fixed.

@AndreasTu
Copy link

looking at the plain Groovy example, what is the purpose of prefixing the assertThat call with assert?

None. It was just a sample without Spock, where AsssertJ is "violating" the Groovy-Truth evaluation.
But I think this does not necessary need to be fixed in AssertJ, because why should a user write assert assertThat(...).isEqualTo() or if(assertThat(...).isEqualTo()).

I understand that Spock introduces a layer of abstraction on top of Groovy, "wiring" the content of the then block with the Groovy assert

Yes, and therefore there is an invisible assert before the assertThat(...).isEqualTo(...), which is unfortunate in this case.
But as I already said in the Spockt ticket, Spock 2.4-M2 introduced the !! prefix to disable the auto assert for a single line.

So when AssertJ is used with Spock (2.4-M2 or later) the user could write:

//Spock code
then:
!! assertThat(someString).isEqualTo('some string')

would prevent the exception.

The only additional thing I could see, is to open a ticket at Groovy and ask them, if they would handle the case where an asBoolean() method not returning a boolean could be handled more gracefully, like evaluating that again with the Groovy-Truth or something.
But this could have more unwanted effects, e.g. you would have a method asBoolean():List, then if the List is empy the value would be false and otherwise true, or an endless-recursion. But this would be a discussion with the Groovy guys.

Or maybe just live with the fact, that usage of AssertJ and Groovy-Truth may throw a ClassCaseException in certain cases.

@scordio
Copy link
Member

scordio commented Mar 1, 2024

The only additional thing I could see, is to open a ticket at Groovy and ask them

I was halfway through it when I realized that it's the usage pattern in the plain example that is not OK. It's like saying that AssertJ doesn't work properly when used inside JUnit or Truth assertions... just don't do it 🙂

But as I already said in the Spockt ticket, Spock 2.4-M2 introduced the !! prefix to disable the auto assert for a single line.

Yes, sorry – what you already wrote went through this time 😅

In light of that, the guideline should be:

  • Spock users on version < 2.4 should stay with AssertJ 2.24.2
  • Spock users on version >= 2.4 can upgrade to a newer AssertJ version and use !! when they encounter problems

What we can do is enhance the AssertJ docs to highlight this pitfall with Spock and Groovy.

@scordio scordio changed the title AbstractStringAssert#asBoolean() overlays Groovy default method DefaultGroovyMethods#asBoolean(java.lang.Object) AssertJ string assertions not working with Spock 2.3 and Groovy Mar 1, 2024
@scordio scordio added this to the 3.26.0 milestone Mar 1, 2024
@scordio scordio self-assigned this Mar 1, 2024
@scordio scordio added the theme: documentation An issue related to documentation label Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3rd-party: Spock An issue related to using AssertJ with Spock language: Groovy An issue related to using AssertJ with Groovy theme: documentation An issue related to documentation
Projects
None yet
Development

No branches or pull requests

5 participants