-
Notifications
You must be signed in to change notification settings - Fork 38.5k
Introduce @Transactional
support for R2DBC in the TestContext framework
#24226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It is currently not possible, since the However, I have reworded the title of this issue and will leave it open for further discussion and brainstorming. @mp911de, feel free to share any ideas you have regarding this topic. |
The only supported approach is moving the We're lacking a propagation model that would allow for reactive Imperative interaction with data and the actual assertions are enclosed within a method. A Reactive transactions require a Another issue are assertions: Assertions are typically modeled using We need to solve both issues to make reactive Basically, from a usage perspective we have two options:
In the first variant, code could look like: @Test @Transactional
void doTest(StepVerifier<Foo> verifier) {
flux.subscribeWith(verifier).expectNext(…).verify();
} or @Test @Transactional
void doTest(MyService myService) {
myService.doWork(…).as(StepVerifier::create).expectNext(…).verify();
}
class MyService() {
@Transactional
Flux<…> doWork() {…};
} Injecting parameters is intrusive and defeats simplicity. Probably, we don't want that kind of experience. It would be also too simple to forget the injection, and tests would fail (or pass) because of a different arrangement. The next example leaves the programming model as-is which looks much more easier to use: @Test @Transactional
void doTest() {
flux.as(StepVerifier::create).expectNext(…).verify();
} Now, how can we associate the transaction with our @Test @Transactional
void doTest() {
flux.as(StepVerifier::create).expectNext(…).verify();
anotherFlux.as(StepVerifier::create).expectNext(…).verify();
} The same utility will work if we use more than one component to test as all reactive flows participate in the transaction. We have only one constraint, which is parallel test execution. Reactor's hooks are JVM-wide (scoped to the Another aspect that plays into the model is that typically, we expect propagation using Reactor's |
any updates on this issue? I see Spring Boot 2.3.0+ includes some updates to R2DBC Support but I am seeing this issue still exists. |
Hey @mp911de! I had some trouble with this lately, and since the @Component
public class Transaction {
private static TransactionalOperator rxtx;
@Autowired
public Transaction(final TransactionalOperator rxtx) {
Transaction.rxtx = rxtx;
}
public static <T> Mono<T> withRollback(final Mono<T> publisher) {
return rxtx.execute(tx -> {
tx.setRollbackOnly();
return publisher;
})
.next();
}
public static <T> Flux<T> withRollback(final Flux<T> publisher) {
return rxtx.execute(tx -> {
tx.setRollbackOnly();
return publisher;
});
}
} Then I can use it on tests like this: @Test
void finds_the_account_and_return_it_as_user_details() {
accountRepo.save(newAccount)
.map(Account::getUsername)
.flatMap(userDetailsService::findByUsername)
.as(Transaction::withRollback) // <-- This makes the test rollback after the transaction
.as(StepVerifier::create)
.assertNext(user -> {
assertThat(user.getUsername()).isEqualTo("[email protected]");
})
.verifyComplete();
} I thought this helper can be used by the Hope this helps, at least as a brainstorm on some ways to solve the issue. Cheers!! |
For the record, this prevents Spring Boot to support any slice test that is transactional. |
Thanks @JoseLion! This worked perfectly. I made some updates for a Kotlin version: import org.springframework.stereotype.Component
import org.springframework.transaction.reactive.TransactionalOperator
import reactor.core.CorePublisher
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@Component
class Transaction(private val transactionalOperator: TransactionalOperator) {
fun <T> withRollback(mono: Mono<T>) = setupRollback(mono).next()
fun <T> withRollback(flux: Flux<T>) = setupRollback(flux)
private fun <T> setupRollback(publisher: CorePublisher<T>) =
transactionalOperator.execute {
it.setRollbackOnly()
publisher
}
} and example test: internal class RepositoryTest(
@Autowired private val repository: SomeRepository,
@Autowired private val transaction: Transaction
) {
@Test
internal fun `round trip data with transaction`() {
val fakeWorkbook = "This is a fake workbook".toByteArray()
repository.save(fakeWorkbook)
.flatMap { repository.read() }
.`as` { transaction.withRollback(it) }
.`as` { StepVerifier.create(it) }
.expectNextMatches { it.contentEquals(fakeWorkbook) }
.verifyComplete()
}
} |
I'm glad this helped @nhajratw 🙂 In case anyone is interested, I went one step further and created a transactional step verifier: public interface TxStepVerifier extends StepVerifier {
static <T> FirstStep<T> withRollback(final Mono<T> publisher) {
return StepVerifier.create(publisher.as(Transactions::withRollback));
}
static <T> FirstStep<T> withRollback(final Flux<T> publisher) {
return StepVerifier.create(publisher.as(Transactions::withRollback));
}
} This makes things a little bit easier to use 😁 so based on the same example above the usage would be: @Test
void finds_the_account_and_return_it_as_user_details() {
accountRepo.save(newAccount)
.map(Account::getUsername)
.flatMap(userDetailsService::findByUsername)
.as(TxStepVerifier::withRollback) // <-- This makes the test rollback after the transaction
.assertNext(user -> {
assertThat(user.getUsername()).isEqualTo("[email protected]");
})
.verifyComplete();
} Cheers! |
@JoseLion using your code as inspiration, I created https://github.com/ChikliC/spring-rxtx-test-support |
@mp911de maybe I found this helpful when you want to have more control over what is transactional and what's not when working on reactive tests. Also, it will provide a good option until I'll be happy to help with a PR to spring-data-r2dbc if you think this could be a good idea 😁 |
Paging @simonbasle. I like the idea of a |
This approach works, mainly because it doesn't really extend Despite being an interface, Only methods from Maybe we can add more composition features to these step interfaces, but I'm afraid it'll never be as smoothly integrated as something like If you think that despite these constraints it could still work for the transaction case, then yeah we need a more precise API design and iterate from there. |
Because of this issue, r2dbc pool cannot be properly used during tests. It keeps on accumulating connections and never releases eventually bringing local db container down. |
Hi, what if we want to test the result of different queries within the same transaction ? We would need to verify intermediate steps, but I don't see how. Why would StepVerifier would need to be inherently blocking ? Ok I think I can do that using Flux.cache() to have the intermediate results stored. |
unit tests are inherently blocking, and so a blocking API approach was most useful. for more advanced cases where you want to control the timing of your checks and assertions, as well as assert intermediate state, reactor-test recently has a |
Thanks what a great timming xD, I will try this, I was on v3.4.12 through spring boot 2.6.1, it's an easy upgrade with 2.6.2. |
I am not sure we mean the same thing with intermediate results. I have multiple R2DBC queries as Fluxes that i want to participate in the same transaction.
If before that i do The test subscriber would have to be in between but i don't see how to do that, or if it's even meant to do that. |
Just wanted to add some information as it might be helpful for my understanding as well as for others - First of all thanks a lot @JoseLion and @nhajratw for sharing such amazing tricks and information. However I figured out that - there are a couple of things which were not configured correctly in my java configs for both the connection factories and also I ended up creating a proper test config class which uses the TransactionOperator as it should as per documentation. I also made sure that I had turned off @EnableTransactionManagement for the test config as well. Hence now I am able to test the repository as expected with automatic rollback after the tests (and yes the data is getting cleaned up automatically after the tests). I will post the code here as it might help a few - my config class -
This is the integration test -
I hope this helps - this is how I achieved automatic transaction rollback in my tests |
@JoseLion - I was finally able to make the code and configuration work as per your suggestion earlier as well which is much cleaner - thank you. |
First of all: Thank you all for that great thread. I've stumbled over the same issues yesterday and was able to solve it for me quickly. Because of this thread. My solution was to create a component of a TrxStepVerifier which can be injected into integration tests. It basically just wraps the StepVerifier. @sgrmgj This is esentially your idea - thank you!
At the moment this is created as a bean inside a configuration class so I can load it easily inside DataR2bcTest together with some other configuration I need. This said: I would be very happy to see some hints how to implement a DataR2dbcTest in the docs. I'm happy to provide some ideas. Maybe someone of you can point me to the right repo/thread to get this started? |
@Transactional
support for R2DBC in the TestContext framework
Since we have
@Transactional
working with R2DBC repositories in 1.0 M2 (as said here), I would like to ask if there is a way to make@Transactional
working with JUnit (integration) tests (the same way we are able to do when using JDBC repositories). Is this currently possible? Will this even be possible? What is the right approach to achieve transactional tests ?Currently, running a
@Transactional
@SpringBootTest
gives mejava.lang.IllegalStateException: Failed to retrieve PlatformTransactionManager for @Transactional test
(the same problem as this guy has: http://disq.us/p/2425ot1).The text was updated successfully, but these errors were encountered: