JUnit: #Rule's overridden apply method not executed under test - java

I created a JUnit rule to rollback a javax.persistence transaction when any exception occurs (otherwise all further tests will fail since the transaction is in an inconsistent condition). The problem is that my rule will never be executed when the test starts, strictly speaking: the apply method is never executed. it does not even work when I put the #Rule declaration into the concrete class and initialize the transactionRule within the test. Here is how it looks like:
THE RULE
public class TransactionRule implements TestRule
{
private EntityManager entityManager;
public TransactionRule(EntityManager entityManager)
{
this.entityManager = entityManager;
}
#Override
public Statement apply(Statement base, Description description)
{
return new TransactionStatement(base);
}
public class TransactionStatement extends Statement
{
private final Statement runningTest;
public TransactionStatement(Statement runningTest)
{
this.runningTest = runningTest;
}
#Override
public void evaluate() throws Throwable
{
try
{
runningTest.evaluate();
}
catch (Exception e)
{
if (entityManager.getTransaction().isActive())
{
entityManager.getTransaction().rollback();
}
}
}
}
}
THE ABSTRACT CLASS THAT USES THE RULE
public abstract class AbstractIntegrationTest
{
//more members vars
#Rule
public TransactionRule transactionRule;
#BeforeClass
public static void setUpBeforeClass()
{
loadProperties();
entityManagerFactory = Persistence.createEntityManagerFactory("MyCuisinePersistenceTestUnit", connectionProperties);
entityManager = entityManagerFactory.createEntityManager();
}
#Before
public void setUp()
{
transactionRule = new TransactionRule(entityManager);
entityManager.clear();
}
//more code
}
THE TEST CLASS WITH THE DEFECTIVE TEST
public class RecipePersistenceITest extends AbstractIntegrationTest
{
//more tests
#Test
public void persistenceOfRecipeWithUserCategorySuccessful()
{
//Test that fails
}
}
Any ideas?

Test rules are invoked before methods annotated with #Before, so trying to assign a rule in #Before will have no effect (although I would have expected it to throw an exception instead).
Instead assign the rule on definition (and make the field final), and do any additional config (if necessary) in your #Before.
Note that each test method is executed in a new instance of the test class, so defining a rule as a final field is no problem.

Related

JPA EntityManager not working when using Guice's PrivateModule

I have a service with a persistence setup using JPA, Hibernate and Guice (if it's useful, I'm not using Spring). This is the first, working version of my code:
public class BookDao {
#Inject
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) {
getEntityManager().persist(book);
}
}
public class MyAppModule extends AbstractModule {
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
final JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
But now I need to configure multiple persistence units. I'm following the advice in this mailing list, and according to them, I should move my module logic to a private module. I did as suggested and created a second version of the same code, the changes are commented below:
#BindingAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ FIELD, PARAMETER, METHOD })
public #interface ProductionDataSource {} // defined this new annotation
public class BookDao {
#Inject
#ProductionDataSource // added the annotation here
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) throws Exception {
getEntityManager().persist(book);
}
}
public class MyAppModule extends PrivateModule { // module is now private
#Override
protected void configure() {
initializePersistence();
// expose the annotated entity manager
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class);
bind(EntityManager.class).annotatedWith(ProductionDataSource.class).toProvider(entityManagerProvider);
expose(EntityManager.class).annotatedWith(ProductionDataSource.class);
}
private void initializePersistence() {
JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
The newly annotated EntityManager is being correctly injected by Guice and is non-null, but here's the fun part: some of my unit tests started failing, for example:
class BookDaoTest {
private Injector injector;
private BookDao testee;
#BeforeEach
public void setup() {
injector = Guice.createInjector(new MyAppModule());
injector.injectMembers(this);
testee = injector.getInstance(BookDao.class);
}
#Test
public void testPersistBook() throws Exception {
// given
Book newBook = new Book();
assertNull(newBook.getId());
// when
newBook = testee.persist(newBook);
// then
assertNotNull(newBook.getId()); // works in the first version, fails in the second
}
}
In the first version of my code the last line above just works: the entity is persisted and has a new id. However, in the second version of my code (using a PrivateModule and exposing an annotated EntityManager from it) the persist() operation doesn't work anymore, the entity is without an id. What could be the problem? I didn't do any other configuration changes in my environment, and I don't see error messages in the logs. Let me know if you need more details.
It turns out that the problem was the #Transactional annotation. In the first version of my code, Guice automatically adds interceptors for managing the transaction. By doing a debug, I found out that before executing my persist(Book book) method, Guice calls the following method from the com.google.inject.internal.InterceptorStackCallback package:
public Object intercept(Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
In the second version of my code, when I exposed the persistence unit from a private module the above interceptor was no longer called, leaving my persist operation without transaction handling. This is a known issue and is by design.
As a workaround I had to implement transactions by hand, making my code more verbose. I also had to change the way the entity manager is injected. This solution worked for me:
public class BookDao {
#Inject
#Named(PROD_PERSISTENCE_UNIT_NAME)
private EntityManagerFactory entityManagerFactory;
private EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void persist(Book book) throws Exception {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(book);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
throw e;
} finally {
em.close();
}
}
}
public class MyAppModule extends PrivateModule {
public static final String PROD_PERSISTENCE_UNIT_NAME = "prod";
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
// persistence unit set to prod DB
final JpaPersistModule jpaPersistModule = new JpaPersistModule(PROD_PERSISTENCE_UNIT_NAME);
// connection properties set to suitable prod values
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
// expose bindings to entity manager annotated as "prod"
bind(JPAInitializer.class).asEagerSingleton();
bind(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).to(PersistService.class).asEagerSingleton();
expose(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManagerFactory.class));
expose(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManager.class));
expose(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(UnitOfWork.class));
expose(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
}
}
As a lesson, be very watchful around annotations and other such "magic" that modifies your code under the hood, finding bugs becomes quite difficult.

Spring bean scope for "one object per test method"

I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})

NPE on spring autowires form TestExecutionListener

This might have been coded wrongly, but any idea how it should be done is appreciated.
I have this class TestClass which needs to inject many service class. Since I can't use #BeforeClass on #Autowired objects, I result on using AbstractTestExecutionListener. Everything was working as expected but when I'm on #Test blocks, all objects are evaluated null.
Any idea how to solve this?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ProjectConfig.class })
#TestExecutionListeners({ TestClass.class })
public class TestClass extends AbstractTestExecutionListener {
#Autowired private FirstService firstService;
// ... other services
// objects needs to initialise on beforeTestClass and afterTestClass
private First first;
// ...
// objects needs to be initialised on beforeTestMethod and afterTestMethod
private Third third;
// ...
#Override public void beforeTestClass(TestContext testContext) throws Exception {
testContext.getApplicationContext().getAutowireCapableBeanFactory().autowireBean(this);
first = firstService.setUp();
}
#Override public void beforeTestMethod(TestContext testContext) throws Exception {
third = thirdService.setup();
}
#Test public void testOne() {
first = someLogicHelper.recompute(first);
// ...
}
// other tests
#Override public void afterTestMethod(TestContext testContext) throws Exception {
thirdService.tearDown(third);
}
#Override public void afterTestClass(TestContext testContext) throws Exception {
firstService.tearDown(first);
}
}
#Service
public class FirstService {
// logic
}
For starters, having your test class implement AbstractTestExecutionListener is not a good idea. A TestExecutionListener should be implemented in a stand-alone class. So you might want to rethink that approach.
In any case, your current configuration is broken: you disabled all default TestExecutionListener implementations.
To include the defaults, try the following configuration instead.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ProjectConfig.class)
#TestExecutionListeners(listeners = TestClass.class, mergeMode = MERGE_WITH_DEFAULTS)
public class TestClass extends AbstractTestExecutionListener {
// ...
}
Regards,
Sam (author of the Spring TestContext Framework)

How should a custom Guice scope be integrated with TestNG?

We use a custom Guice scope, #TestScoped, for some of our JUnit tests that lasts for a single test method, and a JUnit #Rule to enter and exit the scope appropriately. It looks like this:
public class MyJUnitTest {
#Rule public CustomRule customRule = new CustomRule(MyModule.class);
#Inject private Thing thing;
#Test
public void test1() {
// Use "thing"
}
#Test
public void test2() {
// Assuming "Thing" is #TestScoped, we'll have a new instance
}
}
We're starting to use TestNG for some of our tests in other projects, and we'd like to have a similar pattern. So far we've come up with this:
#Listeners(CustomTestNGListener.class)
#Guice(modules = MyModule.class)
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
#Test
public void test1() {
Thing thing = thingProvider.get();
// Use "thing"
}
#Test
public void test2() {
Thing thing = thingProvider.get();
// Assuming "Thing" is #TestScoped, we'll have a new instance
}
}
public class CustomTestNGListener implements IHookable {
#Override
public void run(IHookCallBack callBack, ITestResult testResult) {
TestScope.INSTANCE.enter();
try {
callBack.runTestMethod(testResult);
} finally {
TestScope.INSTANCE.exit();
}
}
}
There are a couple issues with this design:
Unlike JUnit, TestNG uses the same instance of the test class for each method. That means we have to inject Provider<Thing> instead of just Thing, which is awkward.
For some reason, CustomTestNGListener is running on all of our tests, even ones that don't have that #Listeners(CustomTestNGListener.class) annotation. I've worked around this by explicitly checking for that annotation in the listener itself, but it feels like a hack (though I do see that MockitoTestNGListener does the same thing).
Does someone with more familiarity with TestNG have any suggestions for dealing with these issues?
Instead of
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
#Test
public void test1() {
Thing thing = thingProvider.get();
In TestNG you can used
public class MyTestNGTest {
#Inject
private Thing thingInjected;
private Thing thing;
#BeforeTest
public void doBeforeTest() {
thing = thingInjected.clone();
}
Or just call thingProvider.get() in doBeforeTest(), it's better in you have a lot of # Test
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
private Thing thing;
#BeforeTest
public void doBeforeTest() {
thing = thingProvider.get();
}

Get currently executing #Test method in #Before in JUnit 4

I want to get currently executing test method in #Before so that I can get the annotation applied on currently executing method.
public class TestCaseExample {
#Before
public void setUp() {
// get current method here.
}
#Test
#MyAnnotation("id")
public void someTest {
// code
}
}
try TestName rule
public class TestCaseExample {
#Rule
public TestName testName = new TestName();
#Before
public void setUp() {
Method m = TestCaseExample.class.getMethod(testName.getMethodName());
...
}
...
Evgeniy pointed to the TestName rule (which i'd never heard of - thanks, Evgeniy!). Rather than using it, i suggest taking it as a model for your own rule which will capture the annotation of interest:
public class TestAnnotation extends TestWatcher {
public MyAnnotation annotation;
#Override
protected void starting(Description d) {
annotation = d.getAnnotation(MyAnnotation.class);
}
}

Categories

Resources