Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spock - returning fixed value not working as expected

I have to begin by apologising if the terms I use are incorrect. I use groovy/java for automation tasks only(Gradle), and I don't have years of experience delivering production grade software.

So, the challenge that I have is as follows: I have a spec which is trying to test a return string is as expected (almost identical to this).

def "pretty print returns correct string"() {
    setup:
    X509Certificate stubCert = Stub()
    stubCert.getSubjectDN().toString() >> "Test"

    when:
    def output = X509CertificateUtils.prettyPrint(stubCert)

    then:
    output == "Subject: Test"
}

However, the difference is that my method constraint returns a Principal object, and its that object's toString() that I really want to stub. I thought I was doing that correctly above, but it doesn't give the result I expect.

Here's my helper class.

import java.security.cert.X509Certificate

class X509CertificateUtils {

    static def prettyPrint(X509Certificate x509Certificate) {
        return "Subject: ${x509Certificate.getSubjectDN()}"
    }
}

If I run this test I get the following error:

output == "Subject: Test"
|      |
|      false
|      38 differences (20% similarity)
|      Subject: (Mock for )t(ype 'Principal' named 'dummy')
|      Subject: (Tes------)t(-----------------------------)
Subject: Mock for type 'Principal' named 'dummy'

Any help would be gratefully received.

like image 838
Sion Avatar asked Sep 07 '15 17:09

Sion


2 Answers

Just create a second stub:

X509Certificate stubCert = Stub()
Principal princ = Stub()
princ.toString() >> "Test"
stubCert.getSubjectDN() >> princ
like image 113
bezmax Avatar answered Nov 14 '22 22:11

bezmax


Spock has a few approaches to faking objects. Here's the current documentation.

  • Stub: A fake object that returns only what it is told to; a default value otherwise (0, Empty, etc).
  • Mock: Similar to a stub, but it can also test the number of method calls made on the object.
  • Spy: A normal instantiation of your object, the mocks are applied as listeners that intercept calls to the object. This lets you use the object normally, with only the specified methods behaviors changing. It's also possible to call the original code at some point during your mocked behavior.

My question for you... Are you attempting to test that prettyPrint() alone is working properly, that SubjectDN.toString() prints properly, or a combination of the two? My suggestion is to have your mock return an actual SubjectDN() object that you then test as well. Not much more work, and if something breaks you have a better idea of where the problem originated. Max's solution will solve your question; I didn't read close enough or follow good test scoping practices. I'll leave the rest of my answer as food for thought. If you want to mix Max's stub approach with my parameterization I would suggest passing the desired string in the where block to the stub creation in the setup block.

This is beginning to get off topic, but if you need to test more than one SubjectDN scenario (null, empty, various capitalizations, numerics, etc); you should look into parameterizing your test as well.

def "pretty print returns correct string"() {
    setup:
        X509Certificate stubCert = Mock()
        stubCert.getSubjectDN() >> subject 

    expect:
        subject.toString() == expectedToString
        X509CertificateUtils.prettyPrint(stubCert) == expectedFormatted

    where:
        subject | expectedToString | expectedFormatted
        new SubjectDN("") | ???? | "Subject: ????"
        new SubjectDN(null) | ???? | "Subject: ????"
        new SubjectDN("test") | "test" | "Subject: Test"
        new SubjectDN("testing") | "testing" | "Subject: Testing"
}

If your pretty printing really is as simple as prepending "Subject: " you could probably get away with computing your expectedFormatted variable; but you really shouldn't have your test mimic the code you're testing in an attempt to make testing easier.

I also find that the table format of parameterizing the tests gets messy or difficult to maintain when the the iterations have a fluid length. My preference is to make a list of maps, with each map representing a test iteration. It keeps each test iteration bundled together and gives unfamiliar developers a better idea of what each iteration of the test entails.

@Unroll(iteration.testName) // Shows each iteration in its own result (in most IDEs)
def "testing printing of a SubjectDN"() {
    setup:
        X509Certificate stubCert = Mock()
        stubCert.getSubjectDN() >> iteration.subject 

    expect:
        subject.toString() == iteration.expectedToString
        X509CertificateUtils.prettyPrint(stubCert) == expectedFormatted

    where:
        iteration << [
            [testName: "Testing pretty printing a normal SubjectDN",
             subject: new SubjectDN("test"),
             expectedToString: "test"],

            [testName: "Testing pretty printing a normal SubjectDN",
             subject: new SubjectDN("testing 123"),
             expectedToString: "testing 123"],

            [testName: "Testing pretty printing a null SubjectDN",
             subject: new SubjectDN(null),
             expectedToString: ????] 

            // And so on...             
        ]

        expectedFormatted = "Subject: ${iteration.expectedToString}"
}
like image 27
ScientificMethod Avatar answered Nov 14 '22 21:11

ScientificMethod