Repository inserts don't work in SpringBootTest with WebTestClient - spring

I am converting a spring boot application to use webflux by making the endpoints reactive one at a time. Most of the (Spock) tests worked fine, but the ones where I autowire the JPA repository in the test to insert data do not. The repository is always empty when the resource (that we're testing) reads from it. I have tens of tests like this with different repositories and they all have same issue.
Here is an example of a test (the resource we're testing just does findById and returns the example from the repository):
#SpringBootTest
#AutoConfigureWebTestClient(timeout = "60000")
#Transactional
class PaymentControllerIntegrationTest extends Specification {
#Autowired
WebTestClient client
#Autowired
PaymentRepository repo
def "GET /example/{id} returns correct example"() {
given:
def needle = new Example(id: 1L)
def haystack = repo.saveAll([needle, new Example(id: 2L), new Example(id: 3L)])
when:
def response = client.get().uri(EXAMPLE_URL, [id: needle.id.toString()]).exchange()
then:
response.expectStatus().isOk()
response.returnResult(ExampleResponse.class).getResponseBody().blockLast().id == needle.id
}
When I put a breakpoint in the controller and do findAll() the repository is always empty.
I have tried using TestEntityManager.persistAndFlush instead
I have tried using repo.saveAllAndFlush instead
I have tried autowiring ApplicationContext and then building WebTestClient from that, but I never got the autowiring to work
My best guess currently is that the test is setup incorrectly (application context?) so the repository in the test is not the same as the repository in the application.

When using the TestWebClient (or the TestRestTemplate) you are actually issuing a real HTTP request to your started server. This request is handled in a different thread and as such uses a new transaction.
Your test is transactional as well but the data hasn't been committed, another transaction can only read committed data (or you need to set the isolation level to READ_UNCOMMITTED but that is probably not something you should or want to do).
When using MockMvc you are replacing the actual container with a mocked instance and it uses a MockHttpServletRequest etc. and executes in the same thread (and thus reuse the same transaction and can see the data).
To solve, make your test not transactional and cleanup the data afterwards. This will however impact the performance of your tests (as committing and then deleting is slower as rolling back a transaction after the test).

Related

Integration Flow Test Spring Transaction

I'm currently writing a Spock integration test for my Spring application.
I'd like to use #Stepwise in order to perform a test which interacts with the database and then have the next test build on top of the data left behind from the first test.
Unfortunately it seems that a new transaction is started for every test method, thus clearing the data I need to build upon. Rollback(false) does not prevent this behaviour, since the whole transaction is discarded AFAIK.
Here's an example, the MyUserService interacts with a #Repository-interface.
#Stepwise
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
class MyServiceImplIntegrationFlowSpec extends Specification {
#Autowired
#Subject
MyUserService myUserService
#Shared
String userId
void "create user"() {
when:
userId = myUserService.createUser()
then:
userId
}
void "change user permission"() {
when:
myUserService.changePermission(userId, "read")
then:
myUserService.fetchPermission() == "read"
}
}
How can I reuse the data which was created by the previous test method, as is commonly done with #Stepwise, in conjunction with database operations?
The Spring Test framework rolls back the data of each test method by default. You can change this default behaviour by adding the #Commit annotation to each of your test methods where you want to keep the changes in the database. If the whole test suite should commit data to the database I think you can put the #Commit annotation also on class level.
See the reference https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing-tx
It says:
One common issue in tests that access a real database is their effect
on the state of the persistence store. Even when you use a development
database, changes to the state may affect future tests. Also, many
operations — such as inserting or modifying persistent data — cannot
be performed (or verified) outside of a transaction.
and continues describing with
The TestContext framework addresses this issue. By default, the
framework creates and rolls back a transaction for each test. You can
write code that can assume the existence of a transaction. If you call
transactionally proxied objects in your tests, they behave correctly,
according to their configured transactional semantics. In addition, if
a test method deletes the contents of selected tables while running
within the transaction managed for the test, the transaction rolls
back by default, and the database returns to its state prior to
execution of the test. Transactional support is provided to a test by
using a PlatformTransactionManager bean defined in the test’s
application context.
If you want a transaction to commit (unusual, but occasionally useful
when you want a particular test to populate or modify the database),
you can tell the TestContext framework to cause the transaction to
commit instead of roll back by using the #Commit annotation.
Your test case could look like
#Stepwise
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#Commit // if you want all test methods to commit to the database
class MyServiceImplIntegrationFlowSpec extends Specification {
#Autowired
#Subject
MyUserService myUserService
#Shared
String userId
#Commit // if a single test needs to commit to the database
void "create user"() {
when:
userId = myUserService.createUser()
then:
userId
}
void "change user permission"() {
when:
myUserService.changePermission(userId, "read")
then:
myUserService.fetchPermission() == "read"
}
}

Do Spring transactions propagate through new instantiations

I'm working on a bunch of legacy code written by people before me and I'm confused about a particular kind of setup and wonder if this has ever worked to begin with.
There is a managed bean in spring that has a transactional method.
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
public boolean updateDraftAndLivePublicationUsingFastDocumentsOfMySite(List<FastDocumentLite> fastDocumentLites, Long mySiteId) throws Exception { ... }
Now inside that method I find new instantiations calling update methods fe:
boolean firstFeed = new MySiteIdUpdate(publishing, siteDao, siteDomainService).update(siteId, fastDocumentLites.get(0).getMySiteId());
From my understanding on IOC this new class isn't managed by spring , it's just a variable in the bean. Now going further inside the update method you see another service gets called.
#Transactional(propagation=Propagation.REQUIRED, rollbackFor=Throwable.class)
public void activateSubdomainForSite(Long siteId, boolean activationOfSite)
So if there is a transaction open it should be propagated into this service. But here is what I don't get if that MySiteIdUpdate object isn't managed by spring does the first transaction move forward to the activateSubdomainForSite method ?? Or is another transaction being opened here. I looked in the logs and I believe it to be the latter but I rather ask the experts for a second oppinion before I proclame this legacy code to be complete rubbish to the project lead. I'm suffering with a StaleStateException somewhere further down the road and I'm hoping this has anything to do with it.
I think the code is correct, and the second #Transactional should reuse the existing transaction.
Because:
1) Spring Transaction handling is done either by Proxies or by AspectJ advices. If it is done by Proxies then it is required that MySiteIdUpdate invoke an instance that is injected (this is what you did). If you use AspectJ, then it should work anyway.
2) The association Transactions to the code that use is done by the Thread, this mean, as long as you "are" in the thread which started the transaction you can use it. (you do not start an new thread, so it should work)
An other way to explain: It is perfect legal when you have some method in your call hierarchy that does not belong to an spring bean. This should not make the transaction handling fail.

Unit test class annotated by #Transactional and implications to detach/evict in hibernate

I'm struggling with a problem I and can not find out proper solution or even a cause neither in hibernate docs, sources nor S/O.
I have spring/hibernate application with DAO-Service-RPC layers, where DAO provides Hibernate entities and Service DTOs for RPC. Therefore I'm converting (mapping by Dozer) Entities to DTOs in service methods and mapping DTOs back to Entities there as well.
Mapping is as follows (not full method, checking ommited):
#Transactional
public updateAuthor(Author author) {
AuthorEntity existingEntity = this.authorDao.findById(author.getId());
this.authorDao.detach(existingEntity);
this.authorAssembler.toEntity(author, existingEntity, null);
this.authorDao.merge(existingEntity);
}
I have unit test classes annotated by #Transactional to avoid test data bleeding. Now, I realized, that there is something, I don't understand, going on in Service.
When I have my test class annotated by #Transactional, calling detach() seems to work (i.e. Hibernate is not reporting org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session, but #Version number on entity is not incremented properly (as if the parent (unit test)) TX was still holding on.
When I remove my test class annotation, mapping throws org.hibernate.LazyInitializationException: failed to lazily initialize a collection - which makes sense on it's own, because the entity is detached from session. What I don't understand is, why this exception is NOT thrown when the test class is annotated. As I understand, entity is detached from the current session in both cases.
My other question is - assuming the entity is behaving correctly - how to avoid missing such errors in unit tests and avoid test bleeding as well, as it seems to be, this type of error manifests only with unannotated test class.
Thank you!
(JUnit4, Spring 4.0.2.RELEASE, Hibernate 4.3.1.Final)

Commit/Flush transactions during unit test?

I am using Spring and JUnit to write some integration tests for my DAO. I set up my test data in the beginning of the test method, and then test my DAO methods later in the same test method. The problem is that if I don't flush/commit the transaction, the EntityManager returns the same instance of the entities that I have just created in my data setup - rendering my test useless as they will always pass.
E.g.
#Test
#Transactional()
public void loadTreeBasicCase() {
// creates and saved node to DB
Node n = createNode();
// test DAO
Node result = dao.lookup(n.getId());
// verify
assertThat(n, equalTo(result));
}
One way is to expose commit() and/or flush() methods in my DAO. But I would prefer not to do that because in production code, this almost never needs to happen (let EntityManager do it's thing). Is there a way to configure this via annotations or in Spring config? I am using Spring, JPA2 with Hibernate.
You can set the defaultRollback attribute on #Transactional to reset things between tests. This doesn't sound like what you are asking for, just throwing it out there first.
Within the test, the entity manager is behaving correctly. You want to inject different behavior for testing to "disconnect" the setup from the rest of the test. One thing I did in some tests was to call flush on the entity manager directly from the test. I only had to do it a few times, but it was valuable in those cases. I did it in the test (not the DAO) so as to not provide a method on the DAO that I don't want people calling.

Functional test in Spring: changes in database not visible in test

I have integration test (which runs under Jetty) where I open page (by using Selenium) and check that record about this activity was added to database (HSQL). But it does not work -- JPA (Hiberante) adds record (I see it in logs) but when I execute SELECT query there no records at all.
Test case:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath:spring/DispatcherServletContext.xml"
})
#TransactionConfiguration(defaultRollback = false)
public class WhenUserOpenNotExistingPage
extends WhenUserAtAnyPage<NotFoundErrorPage> {
private final String currentUrl;
#Autowired
private SuspiciousActivityDao suspiciousActivities;
private String generateRandomUrl() {
return String.format(
"/tests/page-does-not-exists-%s.htm",
RandomStringUtils.randomNumeric(5)
);
}
public WhenUserOpenNotExistingPage() {
currentUrl = generateRandomUrl();
page.open(currentUrl);
}
#Test
#Transactional(readOnly = true)
public void incidentShouldBeLoggedToDatabase() {
SuspiciousActivity activity =
suspiciousActivities.findByPage(currentUrl);
assertNotNull(activity);
}
}
Also WhenUserOpenNotExistingPage() (constructorr) called twice (and I don't know why it happens and probably is the root of my problem).
Can you help me?
Thanks in advance!
I assume you are adding something to the database in the test case and the same database is being used by your application running on Jetty. If your database uses any isolation level above read uncommited, the changes you made in the test case won't be visible until that test case finishes. This is because you database code joins the transaction that was created when the test was starting.
By default this test transaction is rolled back after the test finishes, so the changes are visible within the current test (transaction), but aren't visible outside (by different threads/connections) and are rolled back. You are changing the default behaviour by using defaultRollback = false attribute, but this only means that changes you made in one test aren't visible by the web application (different database connection), but will be visible in subsequent test (after commit). Not really useful.
You have few options:
Get rid of Spring transactional test support. This means Spring won't create a new transaction every time you start a test and won't do commit/rollback. Now it is up to you when to start transaction and commit it before actually starting SElenium test.
This can easily be done by replacing #TransactionConfiguration with:
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
this will override default test execution listeners, removing TransactionalTestExecutionListener from the stack
You might run setup code from different thread or with propagation REQUIRES_NEW
Finally, you should consider doing setup outside of JUnit. Maybe you can do the setup from the application itself?
As for the constructor - new instance of JUnit test class is created per test. The authors of JUnit claim it makes the test more predictable and stateless (no dependencies between test) by cleaning up the test class prior to running every test. In practice, especially with integration tests, this is more a pain than a advantage.
By the way if you do some database manipulation in the test and commit the changes, watch out for test dependencies. The order in which tests are executed is not guaranteed in JUnit. This means that changes in one test shouldn't affect other tests.

Resources