Search

Dark theme | Light theme
Showing posts with label Spocklight:Utilities. Show all posts
Showing posts with label Spocklight:Utilities. Show all posts

March 30, 2023

Spocklight: Creating Temporary Files And Directories With FileSystemFixture

If we write specification where we need to use files and directories we can use the @TempDir annotation on a File or Path instance variable. By using this annotation we make sure the file is created in the directory defined by the Java system property java.io.tmpdir. We could overwrite the temporary root directory using Spock configuration if we want, but the default should be okay for most situations. The @TempDir annotation can actually be used on any class that has a constructor with a File or Path argument. Since Spock 2.2 we can use the FileSystemFixture class provided by Spock. With this class we have a nice DSL to create directory structures and files in a simple matter. We can use the Groovy extensions to File and Path to also immediately create contents for the files. If we want to use the extensions to Path we must make sure we include org.apache.groovy:groovy-nio as dependency to our test runtime classpath. The FileSystemFixture class also has the method copyFromClasspath that we can use to copy files and their content directory into our newly created directory structure.

In the following example specification we use FileSystemFixture to define a new directory structure in a temporary directory, but also in our project directory:

package mrhaki

import spock.lang.Specification
import spock.lang.Subject
import spock.lang.TempDir
import spock.util.io.FileSystemFixture

import java.nio.file.Path
import java.nio.file.Paths

class FileFixturesSpec extends Specification {

    /**
     * Class we want to test. The class has a method
     * File renderDoc(File input, File outputDir) that takes
     * an input file and stores a rendered file in the given
     * output directory.
     */
    @Subject
    private DocumentBuilder documentBuilder = new DocumentBuilder()

    /**
     * With the TempDir annotation we make sure our directories and
     * files created with FileSystemFixture are deleted after
     * each feature method run.
     */
    @TempDir
    private FileSystemFixture fileSystemFixture

    void "convert document"() {
        given:
        // Create a new directory structure in the temporary directory
        // <root>
        //  +-- src
        //  |    +-- docs
        //  |         +-- input.adoc
        //  |         +-- convert.adoc
        //  +-- output
        fileSystemFixture.create {
            dir("src") {
                dir("docs") {
                    // file(String) returns a Path and with
                    // groovy-nio module on the classpath we can use
                    // extensions to add text to file. E.g. via the text property.
                    file("input.adoc").text = '''\
                    = Sample

                    Welcome to *AsciidoctorJ*.
                    '''.stripIndent()

                    // Copy file from classpath (src/test/resources)
                    // and rename it at the same time.
                    // Without rename it would be
                    // copyFromClasspath("/samples/sample.adoc")
                    copyFromClasspath("/samples/sample.adoc", "convert.adoc")
                }
            }
            dir("output")
        }

        and:
        // Using resolve we get the actual Path to the file
        Path inputDoc = fileSystemFixture.resolve("src/docs/input.adoc")
        Path convertDoc = fileSystemFixture.resolve("src/docs/convert.adoc")

        // We can also use Paths to resolve the actual Path.
        Path outputDir = fileSystemFixture.resolve(Paths.get("output"))

        when:
        File resultDoc = documentBuilder.renderDoc(inputDoc.toFile(), outputDir.toFile())

        then:
        resultDoc.text =~ "<p>Welcome to <strong>AsciidoctorJ</strong>.</p>"

        when:
        File resultConvert = documentBuilder.renderDoc(convertDoc.toFile(), outputDir.toFile())

        then:
        resultConvert.text =~ "<p>Testing <strong>AsciidoctorJ</strong> with Spock 🚀</p>"
    }

    void "convert document from non-temporary dir"() {
        given:
        // Create FileSystemFixture in our project build directory.
        FileSystemFixture fileSystemFixture = new FileSystemFixture(Paths.get("build"))
        fileSystemFixture.create {
            dir("test-docs") {
                dir("src") {
                    dir("docs") {
                        copyFromClasspath("/samples/sample.adoc")
                    }
                }
                dir("output")
            }
        }

        and:
        Path convertDoc = fileSystemFixture.resolve("test-docs/src/docs/sample.adoc")
        Path outputDir = fileSystemFixture.resolve(Paths.get("test-docs/output"))

        when:
        File resultDoc = documentBuilder.renderDoc(convertDoc.toFile(), outputDir.toFile())

        then:
        resultDoc.text =~ "<p>Testing <strong>AsciidoctorJ</strong> with Spock 🚀</p>"

        cleanup:
        // We can delete the test-docs directory ourselves.
        fileSystemFixture.resolve("test-docs").deleteDir()
    }
}

In order to use the Groovy extensions for java.nio.Path we must add the groovy-nio module to the test classpath. For example we can do this if we use Gradle by using the JVM TestSuite plugin extension:

plugins {
    java
    groovy
}

...

repositories {
    mavenCentral()
}

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            // Define we want to use Spock.
            useSpock("2.3-groovy-4.0")
            dependencies {
                // Add groovy-nio module.
                implementation("org.apache.groovy:groovy-nio")
            }
        }
    }
}

Written with Spock 2.3-groovy-4.0 and Gradle 8.0.2.

March 24, 2023

Spocklight: Assert Elements In Collections In Any Order

Since Spock 2.1 we have 2 new operators we can use for assertions to check collections: =~ and ==~. We can use these operators with implementations of the Iterable interface when we want to check that a given collection has the same elements as an expected collection and we don’t care about the order of the elements. Without the new operators we would have to cast our collections to a Set first and than use the == operator.

The difference between the operators =~ and ==~ is that =~ is lenient and ==~ is strict. With the lenient match operator we expect that each element in our expected collection appears at least once in the collection we want to assert. The strict match operator expects that each element in our expected collection appears exactly once in the collection we want to assert.

In the following example we see different uses of the new operators and some other idiomatic Groovy ways to check elements in a collection in any order:

package mrhaki

import spock.lang.Specification;

class CollectionConditions extends Specification {

    void "check items in list are present in the same order"() {
        when:
        List<Integer> result = [1, 10, 2, 20]

        then:
        // List result is ordered so the items
        // are not the same as the expected List.
        result != [20, 10, 2, 1]
        result == [1, 10, 2, 20]

        // We can cast the result and expected list to Set
        // and now the contents and order is the same.
        result as Set == [20, 10, 2, 1] as Set
    }

    void "check all items in list are present in any order"() {
        when:
        List<Integer> result = [1, 10, 2, 20]

        then:
        result ==~ [20, 10, 2, 1]

        /* The following assert would fail:
           result ==~ [20, 10]

           Condition not satisfied:

           result ==~ [20, 10]
           |      |
           |      false
           [1, 10, 2, 20]

           Expected: iterable with items [<20>, <10>] in any order
                but: not matched: <1>*/

        // Negating also works
        result !==~ [20, 10]
    }

    void "lenient check all items in list are present in any order"() {
        when:
        List<Integer> result = [1, 1, 10, 2, 2, 20]

        then:
        // result has extra values 1, 2 but with lenient
        // check the assert is still true.
        result =~ [20, 10, 2, 1]

        /* The following assert would fail:

        result =~ [20, 10]

        Condition not satisfied:

        result =~ [20, 10]
        |      |
        |      false
        |      2 differences (50% similarity, 0 missing, 2 extra)
        |      missing: []
        |      extra: [1, 2]
        [1, 10, 2, 20] */

        // Negating works
        result !=~ [20, 10]
    }

    void "check at least one item in list is part of expected list in any order"() {
        when:
        List<Integer> result = [1, 10, 2, 20]

        then:
        result.any { i -> i in [20, 10]}
    }

    void "check every item in list is part of expected list in any order"() {
        when:
        List<Integer> result = [1, 1, 10, 2, 2, 20]

        then:
        result.every { i -> i in [20, 10, 2, 1]}
    }
}

Written with Spock 2.3-groovy-4.0.