How to write Junit for Interface default methods
Asked Answered
D

5

28

Please help in writing Junit for the interface default method.

public interface ABC<T, D, K, V> {
    default List<String> getSrc(DEF def, XYZ xyz) throws Exception {
    }
}

ABC: Interface Name

DEF and XYZ: Class Names

Dumbbell answered 22/1, 2017 at 11:0 Comment(3)
Can One suggest me, in this issueDumbbell
Possible duplicate of Should we do unit testing for default methods in interfaces (Java 8)?Crabbe
@Arpit, i checked it.. i could not get the correct answer. can u please explain for my exampleDumbbell
G
37

If you're using Mockito, the simplest way to unit-test a default (AKA "defender") method is to make a spy1 using the interface class literal2. The default method can then be invoked on the returned spy instance as normal. The following example demonstrates:

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;

interface OddInterface {
    // does not need any unit tests because there is no default implementation
    boolean doSomethingOdd(int i);

    // this should have unit tests because it has a default implementation
    default boolean isOdd(int i) {
        return i % 2 == 1;
    }
}

public class OddInterfaceTest {
    OddInterface cut = spy(OddInterface.class);

    @Test
    public void two_IS_NOT_odd() {
        assertFalse(cut.isOdd(2));
    }

    @Test
    public void three_IS_odd() {
        assertTrue(cut.isOdd(3));
    }
}

(tested with Java 8 and mockito-2.24.5)

1People often warn using a spy can be indicative of a code or test smell, but testing a default method is a perfect example of when using a spy is a good idea.

2As of the time of this writing (2019), the signature of spy which accepts a class literal is marked as @Incubating, but has been around since mockito-1.10.12 which was released in 2014. Furthermore, support for default methods in Mockito has been around since mockito-2.1.0 which was released in 2016. It seems like a safe bet that this method will continue to work in future versions of Mockito.

Girardo answered 27/2, 2019 at 22:53 Comment(0)
C
6

As suggested in the answer, create implementation class for the interface and test it, for an example I modified getSrc method in your ABC interface, as below:

import java.util.ArrayList;
import java.util.List;

public interface ABC<T, D, K, V> {

    default List<String> getSrc(DEF def, XYZ xyz) {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("default");
        defaultList.add("another-default");
        return defaultList;
    }
}

Created an implementation class for the same, optionally you can create another method calling super method and write @Test for both, as I does:

import java.util.List;

public class ABCImpl implements ABC<String, Integer, String, Integer> {

    public List<String> getSrcImpl(DEF def, XYZ xyz) {
        final List<String> list = getSrc(def, xyz);
        list.add("implementation");
        return list;
    }
}

Corresponding Test class for the implementation is as follows:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class ABCImplTest {

    private ABCImpl abcImpl;

    @Before
    public void setup() {
        abcImpl = new ABCImpl();
    }

    @Test
    public void testGetSrc() throws Exception {
        List<String> result = abcImpl.getSrc(new DEF(), new XYZ());
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("default", "another-default"));
    }


    @Test
    public void testABCImplGetSrc() throws Exception {
        List<String> result = abcImpl.getSrcImpl(new DEF(), new XYZ());
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("default", "another-default", "implementation"));
    }
}
Crabbe answered 22/1, 2017 at 18:36 Comment(7)
Thanks so much. It Worked.Dumbbell
@Dumbbell accept the answer if it worked - meta.stackexchange.com/questions/5234/…Crabbe
Hi, I have one more prblm, How to set value for this hashmap and mock it Map<String, MultiValueMap<String, Integer>> uuidSourceMp = new HashMap<>(3);Dumbbell
open a separate thread for it, don't combine multiple problems in one.Crabbe
I think that maybe creating an inner class or an anonymous class implementation might be better to test the default method, as the implementation might override the default method in the future.Surface
Create anonymous implementation using ABC abc = new ABC(){}; and use abc.getSrc(<<>>).Lode
@DavidBarda is correct, don't create an implementation only for testing purpouses or test the implementation by itself.Bracteole
E
6

The answer is very straight forward. No mocking or spying needed for this just create an anonymous object for interface without overriding default methods.

Ex:

interface Adder {
  default sum(Integer...n) {
    return Arrays.stream(n).reduce(0, Integer::sum);
  }
} 

// Junit 4
class AdderTest {

  private Adder adder;

  @Before
  public void setup() {}
    adder = new Adder(){}; // not overriding default methods
  }

  @Test
  public void testSum() {
    Assert.assertEquals(3, adder.sum(1, 2));
  }
}
Exoteric answered 25/6, 2020 at 6:12 Comment(0)
S
4

I think there is a simpler way. It consists in implementing the interface with methods to be tested in the test class and invoking methods of default type directly. If necessary, the objects that are called internally are mocked. The previous example would be such that:

Interface)

public interface ABC<T, D, K, V> {
    default List<String> getSrc(DEF def, XYZ xyz) throws Exception {
      list<String>() result=new Arraylist<String>();
      result.add(def.toString());
      result.add(xyz.toString());
      return result;
    }
}

Test class)

...        
@RunWith(MockitoJUnitRunner.class)
public class ABCTest implements ABC{

    @Test
    public void testGetSrc() {

        list<String>() result=getSrc(new DEF("Hi!"),new XYZ("bye!"));

        int actual=result.size();
        int expected=2;
        assertEquals(expected, actual);

    }

If you need to test the launch of an exception, it will be enough to force its release from wrong parameters and correctly configure the test:

...        
@Test(expected = GenericRuntimeException.class)
public void test....
...

I've checked it with a similar code and it works. It is also collected correctly in the coverage analysis.

Syzran answered 31/1, 2019 at 12:58 Comment(0)
F
2

You can either create a class that implements your interface or make your test implement it. The second one seems to be a shorter solution:

public class FunctionCallingTransactionTemplateTest implements FunctionCallingTransactionTemplate {
    private final Object object = Mockito.mock(Object.class);

    @Test
    public void should_invoke_function() throws Exception {
        // given
        when(object.toString()).thenReturn("salami");

        // when
        String result = functionCallingTransactionTemplate().execute((status) -> object.toString());

        // then
        assertThat(result).isEqualTo("salami");
    }
}
Filterable answered 23/1, 2017 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.