Reputation: 2917
I have a Maven project with this structure:
-myproject
-myproject-ear
-myproject-service
-webservice
-myproject-ejb
In the myproject-ejb
I have this java packages:
-src/main/java/
-src/test/java/
I have an EJB and the corresponding bean implementation in
-src/main/java/org/mypackage/MyBean.java
-src/main/java/org/mypackage/MyBeanImpl.java
In src/test/java/
I have a test called MyBeanTest.java with the following code:
import javax.ejb.EJB;
import org.mypackage.MyBean;
import org.junit.*;
public class MyBeanTest {
@EJB
private MyBean myBean;
@Test
public void testBean() {
System.out.println("myBean: "+myBean); // prints null
myBean.writeToDB("Hello", "World"); // fails since myBean is null
}
}
When I run the unit test, the myBean
is null. I am wondering why the @EJB
annotation does not work. The test package is in the same application as the bean, so @EJB
should work.
Any ideas?
EDIT 1
I found this link with the same problem as I have, but the solution there doesn´t seem to work for me. Am I doing anything wrong?
package org.myproject.ejb;
import java.util.Hashtable;
import java.util.Properties;
import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import org.myproject.ejb.MyBean;
import org.jboss.ejb.client.ContextSelector;
import org.jboss.ejb.client.EJBClientConfiguration;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;
import org.junit.*;
public class MyBeanTest {
private MyBean myBean;
@Before
public void init() {
try {
Properties clientProp = new Properties();
clientProp.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
clientProp.put("remote.connections", "default");
clientProp.put("remote.connection.default.port", "4447");
clientProp.put("remote.connection.default.host", "localhost");
clientProp.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(clientProp);
ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc);
EJBClientContext.setSelector(selector);
Properties env = new Properties();
env.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
env.put(Context.SECURITY_PRINCIPAL, "admin");
env.put(Context.SECURITY_CREDENTIALS, "testing");
InitialContext ctx = new InitialContext(env);
myBean = (MyBean) ctx.lookup("java:app/myproject-ejb-1.0-SNAPSHOT/MyBeanImpl");
}
catch(NamingException ex) {
ex.printStackTrace();
}
}
@Test
public void testBean() {
System.out.println("ejb: "+myBean); // prints null
}
}
The error I get with the above configuration is:
WARN: Unsupported message received with header 0xffffffff
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:307)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:344)
Upvotes: 0
Views: 10302
Reputation: 475
This is my workaround but I do not like it:
In your test class, use @Inject to retrieve the service. Pass it around where needed. Or @Mocked the InitalContext, in the "new Expectations(){{}}" hookup the lookup and return the instance. If you have @EJB inside a service then you can use Deencapsulation.setField() to inject the dependencies.
Upvotes: 0
Reputation: 23105
Container resource injection, such as @EJB, requires a populated JNDI directory and only works within Java EE managed components executing in a Java EE container. Is a challenge for unit testing. See JSR318 Java EE 6 Platform Spec, section EE.5 Resources, Naming, and Injection.
You're now attempting JNDI lookup - Java SE unit test app remotely connecting its JNDI Context. Disadvantages: must deploy full Java EE 6 app as precondition to run test; test-bugfix-build-deploy-retest lifecycle can slow things.
Some issues:
"ejb:..."
rather than "java:app/..."
because the JBoss EJB-client-project code uses this to intercept the lookup. Also from Java EE 6 platform spec EE.5.2.2: Names in java:app
namespace are shared by all components in all modules in a single Java EE app. If your test is a separate JSE app using java:app
, I suspect JBoss treats it as separate to the single Java EE application, and lookup will fail.Try these actions:
Include these properties:
clientProp.put("remote.connection.default.username", "admin");
clientProp.put("remote.connection.default.password", "testing");
Change client reference:
java:app/myproject-ejb-1.0-SNAPSHOT/MyBeanImpl to
ejb:<app-ear-name>/<module-jar-name>/<jboss-optional-distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>
E.g. if MyBean is a stateless EJB deployed in myproject-ejb-1.0-SNAPSHOT.jar (without any ear). Then:
ejb:/myproject-ejb-1.0-SNAPSHOT//MyBeanImpl!org.mypackage.MyBean
If it's a stateful EJB, then add "?stateful" to string.
Setup ejb-client.properties
directly (via file or program) and apply directly to JNDI Context
. See https://docs.jboss.org/author/display/AS72/EJB+invocations+from+a+remote+client+using+JNDI and https://docs.jboss.org/author/display/AS72/Scoped+EJB+client+contexts and http://middlewaremagic.com/jboss/?p=1177
In future: use CDI for injection; JUnit + CDI @Mock
for "POJO" unit testing; Arquillian for "Java EE" unit/module testing in containers. Then you could avoid/reduce tests like (2) above (JSE client -> EJB).
CDI supports:
@EJB
annotation). This still requires a deployed Java EE app/component and populated JNDI directory to lookup. Managed beans as POJOs or Java EE components (incl. EJBs) - inject "any" to "any" with superior @Inject annotation. Works without JNDI directory, is typesafe & bean scope-aware.
Supports unit testing via simple mocking. Use @Mock
& @Specializes
to declare replacement version for any bean. Test EJB clients without EJBs. Test EJBs as POJOs.
To enable CDI, include a beans.xml
file (can be empty, if all config via annotation).
To declare a managed bean:
@SessionScoped
@Inject
on constructor Use this to inject a reference:
@Inject (optional @MyDeclaredQualifier) private MyBean myBean;
Arquillian ("JUnit for Java EE 6") runs test code itself on a Java EE server. It dynamically deploys test code to configured container(s) and runs tests. It supports @EJB annotation, JNDI connection becomes simple and you can include Java EE classes in unit tests without mocking, or refactoring to abstract away from them.
Upvotes: 5
Reputation: 152
This exception is thrown when no initial context implementation can be created. The policy of how an initial context implementation is selected is described in the documentation of the InitialContext class.
This exception can be thrown during any interaction with the InitialContext, not only when the InitialContext is constructed. For example, the implementation of the initial context might lazily retrieve the context only when actual methods are invoked on it. The application should not have any dependency on when the existence of an initial context is determined.
Upvotes: 1
Reputation: 2360
1) Annotation injection is done by container. So the class which is not managed(container managed) will not be able to do annotation injection.
2) Now, in this scenarios, you will have to make a manual call to JNDI and retrieve EJB instance:
ie:
InitialContext ctx = new InitialContext();
MyBean bean = (MyBeanRemote) ctx.lookup("java:global/<portable jndi name of your bean>");
Note: The use of no arg constructor InitialContext()
. Because your java class is deployed in a server I presume. Or else you may need to specify context factory class if your class is a standalone java class, depending on the vendor.
Note: You will need Bean Remote interface if you are making a call to EJB from a different application (ie: different war, ear ...) or else Local interface is enough.
Upvotes: 2