4

I have the following EJB's:

PersonService.java

@Local
public interface PersonService {
    long countPersons();
}

PersonServiceImpl.java

@Stateless
public class PersonServiceImpl implements PersonService {

    @EJB
    private RemotePersonService remotePersonService;

    @Override
    public long countPersons() {
         return remotePersonService.getAllPersons().size();
    }
}

RemotePersonService.java

@Local
public interface RemotePersonService {
    List<Person> getAllPersons();
}

RemotePersonServiceImpl.Java

@Stateless
public class RemotePersonServiceImpl {
    @Override
    public List<Person> getAllPersons() {
        // Here, I normally call a remote webservice, but this is for the purpose of this question
        List<Person> results = new ArrayList<Person>();
        results.add(new Person("John"));
        return results;
    }
}

And here are my tests

AbstractTest.java

public abstract class AbstractTest {

    private InitialContext context;

    @BeforeClass(alwaysRun = true)
    public void setUp() throws Exception {
        System.setProperty("java.naming.factory.initial", "org.apache.openejb.client.LocalInitialContextFactory");

        Properties properties = new Properties();
        properties.load(getClass().getResourceAsStream("/unittest-jndi.properties"));

        context = new InitialContext(properties);
        context.bind("inject", this);

    }

    @AfterClass(alwaysRun = true)
    public void tearDown() throws Exception {
        if (context != null) {
            context.close();
        }
    }
}

PersonServiceTest.java

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @EJB
    private PersonService personService;

    @Test
    public void testPersonService() {
        long count = personService.countPersons();

        Assert.assertEquals(count, 1l);
    }
}

Now, want I want to do is replace the RemotePersonService implementation in PersonServiceImpl.java by a mock using Mockito, and still have the same call in my testPersonService method.

I tried that:

PersonServiceTest.java

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @Mock
    private RemotePersonService remotePersonService;

    @EJB
    @InjectMocks
    private PersonService personService;

    @BeforeMethod(alwaysRun = true)
    public void setUpMocks() {
        MockitoAnnotations.initMocks(this);

        List<Person> customResults = new ArrayList<Person>();
        customResults.add(new Person("Alice"));
        customResults.add(new Person("Bob"));

        Mockito.when(remotePersonService.getAllPersons()).thenReturn(customResults);
    }

    @Test
    public void testPersonService() {
        long count = personService.countPersons();

        Assert.assertEquals(count, 2l);
    }
}

But this doesn't work. The @Mock RemotePersonService is not injected in the PersonService, and the true EJB is still used.

How can I make this work ?

Yotus
  • 264
  • 1
  • 4
  • 17
  • 1
    Don't use annotations for your tests. Have a constructor that will wire in all your dependencies. Create mocks and pass them to it. – duffymo Jul 14 '15 at 10:25
  • Changing the type _PersonService_ to _PersonServiceImpl_ in the _PersonServiceTest_ apparently does the trick. But shouldn't it be possible to use the interface instead of the implementation ? – Yotus Jul 14 '15 at 10:25
  • You should be explicit here. If you use `PersonService` how do you know which implementation is injected and gets tested? Using `PersonServiceImpl` tells you that. – Kai Jul 14 '15 at 10:27
  • But that's the point - you shouldn't have to know the type that's injected. – duffymo Jul 14 '15 at 10:28
  • It is not allowed to @InjectMocks into interfaces, because you are supposed to be testing concrete classes not interfaces. So as you commented earlier you should replace the interface that mocks are being injected into with a concrete class – Mindaugas Jul 14 '15 at 12:45

2 Answers2

3

Don't use annotations for your tests. Have a constructor that will wire in all your dependencies.

@Stateless
public class PersonServiceImpl implements PersonService {

    @EJB
    private RemotePersonService remotePersonService;

    // Let your test instantiate a mock service and wire it into your test instance using this constructor.
    public PersonServiceImpl(RemotePersonService rps) {
        this.remotePersonService = rps;
    }

    @Override
    public long countPersons() {
         return remotePersonService.getAllPersons().size();
    }
}

Create mocks and pass them to it. Your test might look like this:

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @Test
    public void testPersonService() {
        RemotePersonService mockRemotePersonService = Mockito.mock(RemotePersonService.class);
        List<Person> customResults = new ArrayList<Person>();
        customResults.add(new Person("Alice"));
        customResults.add(new Person("Bob"));
              Mockito.when(mockRemotePersonService.getAllPersons()).thenReturn(customResults);
        PersonService personService = new PersonServiceImpl(mockRemotePersonService);
        long count = personService.countPersons();    
        Assert.assertEquals(count, 2l);
    }
}
duffymo
  • 305,152
  • 44
  • 369
  • 561
  • This wont work since PersonService is an interface and you jut cannot instantiate them. Also writing constructors or setters to be used only in unit tests for injecting mocks is just a bit overhead when one can just inject them using annotations. – Mindaugas Jul 14 '15 at 12:51
  • Typo; should have been PersonServiceImpl. You can instantiate that. Not overhead - trivial. The annotations have to instantiate the objects, too. It's arguable that processing annotations is more overhead than simply instantiating your dependencies. The whole point is that he can't get it to work with the annotations. This will work. – duffymo Jul 14 '15 at 12:53
  • Well using PersonServiceImpl he can get it working with anotations(cause as he said he already did get that working). – Mindaugas Jul 14 '15 at 13:54
  • "Changing the type PersonService to PersonServiceImpl in the PersonServiceTest apparently does the trick." - apparently yes. – Mindaugas Jul 14 '15 at 14:06
  • I can make it work using the concrete class PersonServiceImpl in the test, but I wanted to know if it was possible to use the interface instead. But apparently it is inherently impossible to do so because of how Mockito injects mocks. – Yotus Jul 15 '15 at 08:02
0

I use setters on the class, and Lookup for the ejb

      private ServicioAsyncMessaging servicioNotificaciones;

I delete the @EJB --> and on the getter

    public ServicioAsyncMessaging getServicioNotificaciones() {
            if(servicioNotificaciones == null){
             servicioNotificaciones = (ServicioAsyncMessaging)Lookup.getEjb(EjbJndiConstantes.EJB_SERVICIO_ASYNC_MSG);
            }
            return servicioNotificaciones;
        }
    
        public void setServicioNotificaciones(ServicioAsyncMessaging servicioNotificaciones) {
            this.servicioNotificaciones = servicioNotificaciones;
        }

The lookup es:

public static Object getEjb(String lookupName){
    Object t = null;
    try {
        Context ctx = new InitialContext();
         t=  ctx.lookup(lookupName);
    } catch (NamingException e) {
        log.error("getEjb | Error {}",e.getMessage(),e);
    }
    return t;
}

With those changes the mockito --> inject the mocks on the setter.

cabaji99
  • 1,305
  • 11
  • 9