Search code examples
javaejbmockitoejb-3.1jboss-arquillian

EJB no interface view testing (arquillain & mockito)


I am working on a Java EE 7 (on wildfly 9.0.2) application and I stumbled on an article http://www.oracle.com/technetwork/articles/java/intondemand-1444614.html. Mainly about:

Premature Extensibility Is the Root of Some Evil

This makes sense in certain cases I have encountered. I have changed some classes to a no interface view. The implementation itself is not a problem, testing however is.

For example I have these 2 classes.

@Stateless
public class SomeBean {
     public String getText()
     {
         return "Test text";
     }
}

And

@Stateless
public class SomeOtherBean {
    @Inject
    private SomeBean someBean;

    public String getText()
    {
        return someBean.getText();
    }
}

I want somehow that the someBean property is overwritten with preferably a mocked object. Without altering the SomeBean and SomeOtherBean class. I have tried some examples, but they didn't work for example: https://github.com/arquillian/arquillian-showcase/tree/master/extensions/autodiscover/src/test/java/org/jboss/arquillian/showcase/extension/autodiscover

Has anyone encountered this before and have a solution?


Solution

  • I ended up using 2 solutions.

    Solution 1: Use mockito for internal or smaller tests

    For testing a particular class Mockito is really useful, as it supports dependency injection.

    @RunWith(MockitoJUnitRunner.class)
    public class SomeOtherBeanTest {
        @Mock
        private SomeBean someBean;
    
        @InjectMocks
        private SomeOtherBean someOhterBean;
    
        @Before
        public void setUp() {
            Mockito.when(someBean.getText()).thenReturn("Overwritten!");
        }
    
        @Test
        public void testGetText() throws Exception {
            assertEquals("Overwritten!", someOhterBean.getText());
        }
    }
    

    Solution 2: Use @Produces and @Alternatives for mocking external services (e.g. mocking OAuth2 server) or larger test (e.g. integration testing)

    First I create a new @Alternative annotation:

    @Alternative
    @Stereotype
    @Retention(RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
    public @interface CDIMock {}
    

    Then add this as stereotype to the arquillian beans.xml deployment:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
           bean-discovery-mode="all">
        <alternatives>
            <stereotype>com.project.CDIMock</stereotype>
        </alternatives>
    </beans>
    

    After that create a new @Producer method in a seperate class:

    public class SomeBeanMockProducer {
        @Produces @CDIMock
        public static SomeBean produce() {
            SomeBean someBean = Mockito.mock(SomeBean.class);
            Mockito.when(someBean.getText()).thenReturn("mocked");
    
            return someBean;
        }  
    }
    

    Add the SomeBeanMockProducer class to the arquillian deployment and you should have it working.

    An alternative to this solution is using @Specializes and extending the SomeBean implementation. In my opinion this doesn't give me enough control like the @Alternative + Mocking solution (@CDIMock in my example).

    For example, lets say I SomeBean has methods that calls remote servers. If I add a method to this and forget to @override this in the @Specializes class it will make a real remote call, this won't be the case with Mocking.