How to use spock to stub/replace super method in unit test

50 views Asked by At

I've inherited a massive code base and need to add some unit tests, below is a representation of the scenario under test.

Given these two classes:

package com.foo;

public class ExtendableClazz {
    public String superClazzMethod(String str) {
        return " A:" + str;
    }

    // ----------- THIS IS THE METHOD I WANT TO NOT CALL I.E REPLACE IN THE TEST -------------
    public String methodWithSuper(String str) {
        return " B:" + str;
    }
}

and:

package com.foo;

public class ExampleClazz extends ExtendableClazz {
    public String exampleClazzMethod(String str) {
        return "C:" + str;
    }

    public String methodWithSuper(String str) {
        String result = "";
        result += exampleClazzMethod(str);
        result += superClazzMethod(str);
        result += super.methodWithSuper(str);
        return result;
    }
}

How do I stub the super.methodWithSuper(str) in the second class. My spock test so far (which fails is:

package com.foo

import spock.lang.Specification

class ExampleClazzTest extends Specification {
    def "makeString #inputStr"() {
        when:
            ExampleClazz clazz = new ExampleClazz() {
                // THIS DOES NOT WORK IT OVERRIDES THE METHOD IN THIS CLASS NOT ITS SUPER
                @Override
                String methodWithSuper(String str) {
                    return " STUB:" + str
                }
            }

        then:
            clazz.methodWithSuper(inputStr) == result

        where:
            inputStr || result
            "foo"    || "C:foo A:foo STUB:foo"
            "bar"    || "C:bar A:bar STUB:bar"
    }
}

Basically I don't want to call the real "super.methodWithSuper" I want to fake it.....

I've tried searching and mocking, closest I could get is in the code above.

2

There are 2 answers

0
Vampire On

I don't think you can do that without using some byte-code manipulation library that changes the actual bytecode of the superclass which would be a pretty advanced topic.

It would make much more sense to refactor the code under test to make it better testable without needing something like that.

0
kriegaex On

Like @Vampire said, just refactor your code for better testability. In this case, it is enough to simply extract the functionality you want to stub into a method extracted(String):

package de.scrum_master.stackoverflow.q77586089

import spock.lang.Specification

class ExampleClazzTest extends Specification {
  def "makeString #input"() {
    given:
    def subAnonymous = new Sub() {
      @Override
      String baseWithOverride(String str) {
        super.baseWithOverride(str) + " STUB:" + str
      }
    }
    Sub sub = Spy(subAnonymous)
    sub.extracted(_) >> ""

    expect:
    sub.baseWithOverride(input) == expectedResult

    where:
    input | expectedResult
    "foo" | "C:foo A:foo STUB:foo"
    "bar" | "C:bar A:bar STUB:bar"
  }
}
package de.scrum_master.stackoverflow.q77586089

class Base {
  String baseNoOverride(String str) { " A:" + str }
  String baseWithOverride(String str) { extracted(str) }
  protected String extracted(String str) { " B:" + str }
}
package de.scrum_master.stackoverflow.q77586089

class Sub extends Base {
  String subNoOverride(String str) { "C:" + str }

  @Override
  String baseWithOverride(String str) {
    subNoOverride(str) + baseNoOverride(str) + super.baseWithOverride(str)
  }
}

Try it in the Groovy Web Console.