jonesir
jonesir

Reputation: 33

Mock Instance<Class<?>> annotated with Inject&Any in Junit

In my javaee project there is an interface:

public interface SomeInterface{...}

and multiple implementations:

@Stateless(name = "ImplementationA")
public class ImplementationA implements SomeInterface{...}

@Stateless(name = "ImplementationB")
public class ImplementationB implements SomeInterface{...}

In order to access all of the implementations, I have the following in an other class:

@Singelton
public class AnotherClass{
    @Inject
    @Any
    private Instance<SomeInterface> impls;

    public SomeInterface someMethod(){
        for(SomeInterface imp : impls){
            if(imp.oneMethod()){
                return imp;
            }
        }
        return null;
    }
}

If I want to do unit test for this "AnotherClass", how do I mock the

Instance<SomeInterface> impls

field?

Tried @Mock, @Spy, could not get "impls" properly mocked from within Mockito, when the test runs, the "impls" is always null.

The Unit test itself looks like the following:

@RunWith(MockitoJUnitRunner.class)
public class SomeTestClass {
    @InjectMocks
    AnotherClass anotherClass;

    @Spy // Here I tried @Mock as well
    private Instance<SomeInterface> impls;

    @Test
    public void TestSomeMethod(){
        Assert.assertTrue( anotherClass.someMethod() == null ); // <- NullPointerException here, which indicates the impls is null instead of anotherClass.
    }
}

Had to add another method in that "AnotherClass" to accept an instance of Instance impls, which is created in unit test, which works but is ugly that another irrelevant method has to be added only for the purpose of unit test.

Any idea what the proper way of doing unit test looks like?

Mockito and Junit version:

group: 'junit', name: 'junit', version: '4.12'
group: 'org.mockito', name: 'mockito-core', version:'2.12.0'

Thanks in advance.

Upvotes: 1

Views: 1092

Answers (1)

ikos23
ikos23

Reputation: 5354

What you could try to do:

  1. Add some expectations if you need them. You probably need this impls.xxxx() to call a real method if it is a Spy (guess this is default behavior).

  2. Maybe also try to init mocks first:

    @RunWith(MockitoJUnitRunner.class)
    public class SomeTestClass {
        @InjectMocks
        AnotherClass anotherClass;
    
        @Spy  
        private Instance<SomeInterface> impls;
    
        // init here
        @Before 
        public void initMocks() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void TestSomeMethod(){
            anotherClass.someMethod(); // <- NullPointerException here, which indicates the impls is null instead of anotherClass.
        }
    }
    

This init call needs to be somewhere in the base class or a test runner.

That's weird it does not work without, I guess if you use MockitoJUnitRunner it should work.

UPD:


It's been a long time but I can see there are some new comments so providing additional input.

This is the test that works.

// ImplementationA.oneMethod simply returns TRUE in my case
// ImplementationB.oneMethod simply returns FALSE
@RunWith(MockitoJUnitRunner.class)
public class AnotherClassTest {

    @Spy // can be Mock
    Instance<SomeInterface> impls;

    @InjectMocks
    AnotherClass classUnderTest;

    @Mock
    Iterator<SomeInterface> iterator; // why need it - check below :)

    @Test
    public void someMethod() {
        when(impls.iterator()).thenReturn(iterator);
        when(iterator.hasNext()).thenReturn(true).thenReturn(false);
        when(iterator.next()).thenReturn(new ImplementationA());
        
        SomeInterface res = classUnderTest.someMethod();
        System.out.println("done");
    }
}

Where was the problem ? Here:

public SomeInterface someMethod() {
    
    // explanation: For-Each uses iterator
    // if we do not mock Instance<SomeInterface> impls properly
    // impls.iterator() under the hood will return NULL -> NPE
    for (SomeInterface imp : impls) {
        if (imp.oneMethod()) {
            return imp;
        }
    }
    return null;
}

That is why in my test I also create dummy iterator (Mock). I also need to provide some expectations to make it work and here they are:

when(impls.iterator()).thenReturn(iterator); // returns my mock
when(iterator.hasNext()).thenReturn(true).thenReturn(false);
when(iterator.next()).thenReturn(new ImplementationA());

Hope it's clear :) Having this make the for-each works fine and returns ImplementationA.

Happy Hacking :)

Upvotes: 1

Related Questions