Testing Kotlin with Spock Part 3 – Interface default method

Kotlin allows you to put method implementation in an interface. The same mechanism can be found in Java interfaces as default methods (and also Groovy or Scala traits). Let’s see the difference between the Kotlin and Java default methods in interface by testing it with Groovy and Spock.

What do we want to test?

We often have an interface for access object from the database. In domain, they might look similar to this KotlinOrderRepository:

interface KotlinOrderRepository {
    fun save(order: Order)

    fun find(orderId: OrderId): Order?

    fun get(orderId: OrderId): Order =
            find(orderId) ?: throw NotFound()
}

How to fake it with Groovy?

When we want to use such interface in tests, we can, of course, mock it. However, it is far better to fake repositories with a simple, in-memory implementation. Let’s create FakeKotlinOrderRepository in Groovy:

class FakeKotlinOrderRepository implements KotlinOrderRepository {
    private Map<OrderId, Order> data = [:]

    @Override
    void save(Order order) {
        data[order.id] = order
    }

    @Override
    Order find(OrderId orderId) {
        return data[orderId]
    }
}

Unfortunately, this causes a compilation error

/testing-kotlin-in-spock/src/test/groovy/com/github/alien11689/testingkotlinwithspock/defaultmethod/FakeKotlinOrderRepository.groovy: 3: Can't have an abstract method in a non-abstract class. The class 'com.github.alien11689.testingkotlinwithspock.defaultmethod.FakeKotlinOrderRepository' must be declared abstract or the method 'com.github.alien11689.testingkotlinwithspock.defaultmethod.Order get(com.github.alien11689.testingkotlinwithspock.defaultmethod.OrderId)' must be implemented.
 @ line 3, column 1.
   class FakeKotlinOrderRepository implements KotlinOrderRepository {
   ^

1 error

The compiler doesn’t see the implementation of the get method in the Kotlin interface. We have to use some magic to make it work in groovy.

Solution

To solve the problem, let’s look into the generated classes:

$ ls build/classes/main/com/github/alien11689/testingkotlinwithspock/defaultmethod/
JavaOrderRepository.class
KotlinOrderRepository.class
KotlinOrderRepository$DefaultImpls.class
NotFound.class
Order.class
OrderId.class

The KotlinOrderRepository$DefaultImpls class is the one we’re looking for as we can use it in Groovy to implement the missing operation.

class FakeKotlinOrderRepository implements KotlinOrderRepository {

    // ...

    Order get(OrderId orderId) {
        return KotlinOrderRepository.DefaultImpls.get(this, orderId)
    }
}

Now the code compiles and tests pass:

class KotlinRepositoryWithDefaultMethodTest extends Specification {
    OrderId orderId = new OrderId(UUID.randomUUID() as String)
    Order order = new Order(orderId, 'data')
    KotlinOrderRepository kotlinOrderRepository = new FakeKotlinOrderRepository()

    def 'should get order from kotlin repository'() {
        given:
            kotlinOrderRepository.save(order)
        expect:
            kotlinOrderRepository.get(orderId) == order
    }

    def 'should throw NotFound when order does not exist in kotlin repository'() {
        when:
            kotlinOrderRepository.get(orderId)
        then:
            thrown(NotFound)
    }
}

Is there the same problem with Java?

Let’s have a quick look at how this works with Java interfaces. If we write a similar repository in Java:

public interface JavaOrderRepository {
    void save(Order order);

    Optional<Order> find(OrderId orderId);

    default Order get(OrderId orderId) {
        return find(orderId).orElseThrow(NotFound::new);
    }
}

and create a fake implementation in Groovy:

class FakeJavaOrderRepository implements JavaOrderRepository {
    private Map<OrderId, Order> data = [:]

    @Override
    void save(Order order) {
        data[order.id] = order
    }

    @Override
    Optional<Order> find(OrderId orderId) {
        return Optional.ofNullable(data[orderId])
    }
}

there is no compilation error and the tests pass:

class JavaRepositoryWithDefaultMethodTest extends Specification {
    OrderId orderId = new OrderId(UUID.randomUUID() as String)
    Order order = new Order(orderId, 'data')
    JavaOrderRepository javaOrderRepository = new FakeJavaOrderRepository()

    def 'should get order from java repository'() {
        given:
            javaOrderRepository.save(order)
        expect:
            javaOrderRepository.get(orderId) == order
    }

    def 'should throw NotFound when order does not exist in java repository'() {
        when:
            javaOrderRepository.get(orderId)
        then:
            thrown(NotFound)
    }
}

Groovy can implement Java interfaces with the default methods without any problems.

Show me the code

Code is available here.

You May Also Like

TouK na targach pracyTouK at the job fair

Zapraszamy na XI Targi Pracy i Praktyk dla Elektroników i Informatyków. Odwiedź nasze stoisko w dniach 4-5 marca w godz. 9:30-15:30. Politechnika Warszawska Pierwsze piętro budynku Wydziału Elektroniki i Techniki Informacyjnych Nowowiejska 15/19We invite you to 11th Job and Internship Fair for Electronic Engineers and IT Specialists. Come and visit our stand between 4-5 March 9:30 am and 15 :30 pm Warsaw University of Technology the first floor of the Electronics faculty building Nowowiejska 15/19

Grails with Spock unit test + IntelliJ IDEA = No thread-bound request found

During my work with Grails project using Spock test in IntelliJ IDEA I've encountered this error:

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.codehaus.groovy.grails.plugins.web.api.CommonWebApi.currentRequestAttributes(CommonWebApi.java:205)
at org.codehaus.groovy.grails.plugins.web.api.CommonWebApi.getParams(CommonWebApi.java:65)
... // and few more lines of stacktrace ;)

It occurred when I tried to debug one of test from IDEA level. What is interesting, this error does not happen when I'm running all test using grails test-app for instance.

So what was the issue? With little of reading and tip from Tomek Kalkosiński (http://refaktor.blogspot.com/) it turned out that our test was missing @TestFor annotation and adding it solved all problems.

This annotation, according to Grails docs (link), indicates Spock what class is being tested and implicitly creates field with given type in test class. It is somehow strange as problematic test had explicitly and "manually" created field with proper controller type. Maybe there is a problem with mocking servlet requests?