Search code examples
javaejbtestngmockitoopenejb

Unit testing an EJB with Mockito, TestNG and OpenEJB


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 ?


Solution

  • 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);
        }
    }