Use JUnit Rule TemporaryFolder in Module

147 views
Skip to first unread message

ich du

unread,
Feb 10, 2014, 9:15:28 AM2/10/14
to juk...@googlegroups.com
For testing a JPA application i need to run tests with a file-data base (derby). As database file i want to use a file within "TemporaryFolder" delivered by JUnit @Rule. (The test data base must be deleted after the test)

So i need the EntityManager to be injected after @Before (the time junit initializes all rules). So i tried to bind a Provider<EntityManager> to the test class itself that implements the appropriate get. Here is an example for a mini test class:

@RunWith(JukitoRunner.class)
public class InjectionTest implements Provider<EntityManager> {

public static class Module extends JukitoModule {

@Override
protected void configureTest() {
bind(VotingService.class).to(VotingServiceProducer.class).in(
TestSingleton.class);
bind(EntityManager.class).toProvider(InjectionTest.class).in(
TestSingleton.class);
}

}
private static final String PERSISTENCE_UNIT_NAME = "votingTest";
@Rule
public TemporaryFolder tf = new TemporaryFolder();
@Override
public EntityManager get() {
File dbFile;
try {
dbFile = tf.newFile();
} catch (IOException e) {
throw new IllegalStateException(
"Problem creating file for test db.");
}

Map<String, String> config = new HashMap<>();
config.put("javax.persistence.jdbc.url", "jdbc:derby:" + dbFile
+ ";create=true");
EntityManagerFactory factory = Persistence.createEntityManagerFactory(
PERSISTENCE_UNIT_NAME, config);
return factory.createEntityManager();
}
@Inject
private Provider<VotingService> vSProv;

@Before
public void init() {
vSProv.get();
}
@Test
public void some(){
System.out.println("blub");
}
}

The voting service needs the entity manager. The idea is not to inject the voting service directly but within @Before (after TemporaryFolder is available). But get() is called before @Before and before the first EntityManager is needed?! Did i something wrong?
Is there any other way to us the temporaryFolder within the module. I also tried java methods for getting temporary folder but "deleteOnExit" is not working properly.

Stephan Classen

unread,
Feb 10, 2014, 9:21:19 AM2/10/14
to juk...@googlegroups.com
could you also post the code of the VotingService and VotingServiceProducer.
Just to enable us to reproduce you outcome.
--
You received this message because you are subscribed to the Google Groups "Jukito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jukito+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

ich du

unread,
Feb 10, 2014, 9:42:31 AM2/10/14
to juk...@googlegroups.com
Here is VotingServiceProducer:

public class VotingServiceProducer implements VotingService {

private EntityManager em;

@Inject
private VotingServiceProducer(EntityManager em) {
this.em = em;
}

@Override
public String createVoting(Voting voting) {
VotingImpl votingImpl = new VotingImpl(voting);
em.getTransaction().begin();
em.persist(votingImpl);
em.getTransaction().commit();
return String.valueOf(votingImpl.getId());
}

@Override
public Voting getVotingById(String id) {
em.getTransaction().begin();
Voting voting = em.find(VotingImpl.class, Long.parseLong(id));
em.getTransaction().commit();
return voting;
}

}


The interface is given implicitly, it simply contains the two methods. The problem is that JukitoRunner calls the Provider.get before the first EntityManager is needed?!

Stephan Classen

unread,
Feb 10, 2014, 9:48:44 AM2/10/14
to juk...@googlegroups.com
OK
The problem is the following:


        @Inject
private VotingServiceProducer(EntityManager em) {
this.em = em;
}

This has 2 effects which you most likely don't want:
1)
since you bind the VotingService as singelton it will eagerly be created at the time of injector creation which is before the @Befoer method. This is what you see in you test.
2)
Binding the EntityManager is the most common source of error when using GuicePersist.
The EntityManager is only valid for a single UnitOfWork. This means as soon as your current UnitOfWork ends the injected EntityManager is closed and any subsequent call to any of its methods will throw an exception (except isClosed() ).
Therefore you should almost allways inject a Provider<EntityManager> into your service and only obtain a EntityManager within a method, discarding it after the method finished.

Stephan Classen

unread,
Feb 10, 2014, 9:53:22 AM2/10/14
to juk...@googlegroups.com
This should work:

public class VotingServiceProducer implements VotingService {

private final Provider<EntityManager> emProvider;

@Inject
private VotingServiceProducer(Provider<EntityManager> emProvider) {
this.emProvider = emProvider;
}

@Override
public String createVoting(Voting voting) {
                final EntityManager em = emProvider.get();
final VotingImpl votingImpl = new VotingImpl(voting);
em.getTransaction().begin();
em.persist(votingImpl);
em.getTransaction().commit();
return String.valueOf(votingImpl.getId());
}

@Override
public Voting getVotingById(String id) {
                final EntityManager em = emProvider.get();
 
em.getTransaction().begin();
final Voting voting = em.find(VotingImpl.class, Long.parseLong(id));
em.getTransaction().commit();
return voting;
}

}




On 02/10/2014 03:42 PM, ich du wrote:

ich du

unread,
Feb 10, 2014, 10:29:21 AM2/10/14
to juk...@googlegroups.com
Thanks for pointing to giuce-persist (didn't know about that). But an example there looks like that:

import com.google.inject.persist.Transactional;
import javax.persistence.EntityManager;
 
public class MyService {
       
@Inject EntityManager em;
 
       
@Transactional
       
public void createNewPerson() {
                em
.persist(new Person(...));
       
}
}

No using of a provider?! It seems guice-persist is managing all kinds of stuff. But how this  solves my problem?
I need to set a temporary folder as url within persistent unit. So in my test i can't use new JpaPersistModule("myFirstJpaUnit"). Because i must programatically set the temporary folder.

Stephan Classen

unread,
Feb 11, 2014, 1:54:05 AM2/11/14
to juk...@googlegroups.com
first of all: about guice-persist: https://groups.google.com/forum/#!topic/google-guice/Dp9nWdmDbUs

back to your tests:

after going through your code examples one more time. I think what you are trying to achieve is a clean DB for every execution of a test method.
you try to do this by creating a new DB file for every test method. this means you have to recreate the entire DB schema for every test method.

I would propose a different approach.
- create the DB once for your entire suit of tests.
- wrap every test method in a transaction which is rolled back at the end of the method (this can also be done with a @Rule)

The main advantage is, that is much simpler to implement and it will also speed up your test suit since the DB can be reused.

This approach has some limitations:
- It won't work if you need to test transaction handling in your service methods (e.g. that a rollback happens in a certain scenario)
- It won't work if your methods under test change the DB schema (e.g. create, drop or alter tables/views)
- It can have have sideffects when the tests are run by multiple threads in parallel and the transaction isolation is too low (see: http://www.code2learn.com/2012/05/transaction-isolation-levels-in-jdbc.html)

But if more than 80% of your tests which require a DB do just regular CRUD operations I would highly recommend reusing the DB.

If this approach would work for you I could give you some code snipped to get started.

Stephan Classen

unread,
Feb 11, 2014, 3:07:02 AM2/11/14
to juk...@googlegroups.com
In the mean time (yes I am a little bored right now and your problem
seems interesting :) )

This should create a new DB Instance for every test.
I don't know if this works but you could give it a try:
Also note the persistService.start and stop as well as the
UnitOfWork.begin and end method calls in @Before and @After
The @Before and @After methods (and the TemporaryFolder) could be moved
to a new TestRule which would be reusable for different test classes


@RunWith(JukitoRunner.class)

public class InjectionTest {

private static final String PERSISTENCE_UNIT_NAME = "votingTest";

private static final String TEST_DB_PROPS = "testDbProps";

public static class Module extends JukitoModule {

@Override

protected void configureTest() {

final Properties testDbProps = new Properties();

bind(VotingService.class).to(VotingServiceProducer.class).in(

TestSingleton.class);

bind(Properties.class).annotatedWith(Names.named(TEST_DB_PROPS)).toInstance(testDbProps);

final JpaPersistModule persistModule = new JpaPersistModule(PERSISTENCE_UNIT_NAME);

persistModule.properties(testDbProps);

install(persistModule);

}

}

@Rule

public TemporaryFolder tf = new TemporaryFolder();

@Before

public void createAndStartDB(PersistService perService, UnitOfWork unitOfWork, @Named(TEST_DB_PROPS) Properties testDbProps) throws IOException {

final File dbFile = tf.newFile();

testDbProps.clear();

testDbProps.setProperty("javax.persistence.jdbc.url", "jdbc:derby:" + dbFile + ";create=true");

perService.start();

unitOfWork.begin();

}

@After

public void tearDownDB(PersistService perService, UnitOfWork unitOfWork) {

unitOfWork.end();

perService.stop();
Reply all
Reply to author
Forward
0 new messages