Albert Bos
Albert Bos

Reputation: 2062

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?

Upvotes: 3

Views: 794

Answers (2)

Albert Bos
Albert Bos

Reputation: 2062

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.

Upvotes: 5

jhyot
jhyot

Reputation: 3955

It is kind of obvious that substituting a Mock or other specialized object for an injected no-interface class is more difficult, since that is exactly what you wanted by not declaring an interface for the bean.

Having said that, if you are not running a CDI container (e.g. doing POJO unit tests) I think using Mockito is the easiest way.

If you want to do it the "pure CDI" way within a container, you can use the Alternative and Specialization mechanisms of CDI, as described in the Java EE 6 tutorial.

@Specializes
public class SomeBeanMock extends SomeBean {

    @Overrides         
    public String getText()
     {
         return "mock";
     }
}

Of course you can only use mocks that subclass the original bean (since you have no interface) and you are limited to the usual visibility rules. Changing/mocking private field or methods would require reflection or bytecode manipulation (which is what Mockito does behind the scenes).

Upvotes: 0

Related Questions