Wątek przeniesiony 2024-01-31 21:22 z Inżynieria oprogramowania przez somekind.

Problem z @Transactional w testach

0

Cześć,
chcę przetestować proces rejestracji użytkownika z użyciem Testcontainers. W moich teście występuje błąd. Test posiada adnotację @Transactional - korzystam z niej w celu cofnięcia zmian w bazie danych po zakończeniu testu. O dziwo, kiedy usunę tę adnotację, test przechodzi. Dlaczego po dodaniu tej adnotacji test nie przechodzi? Jak mogę to rozwiązać?

Edit.
Nie wiem czy to ważne, ale metoda register w implementacji również posiada adnotację @Transactional.

Test:

public class RegistrationServiceTest extends BaseIT {
    @Autowired
    private RegistrationService registration;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private LoginService login;
    private DatabaseHelper databaseHelper;

    @BeforeEach
    void setUp() {
        databaseHelper = new DatabaseHelper(dataSource);
    }

    @Test
    @Transactional
    void whenValidCredentials_RegisterUser() {
        registration.register(RegistrationHelper.REGISTER("Alex"));
        assertTrue(databaseHelper.userExist("Alex"));
    }
}

Helper:

@Slf4j
public class DatabaseHelper {

    private final DataSource dataSource;

    public DatabaseHelper(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public boolean userExist(String username) {
        try {
            String query = "SELECT COUNT(*) FROM users WHERE username = ?";
            try (Connection connection = dataSource.getConnection();
                 PreparedStatement statement = connection.prepareStatement(query)) {
                    statement.setString(1, username);
                    log.info("Execution of the query: {}", query);
                    ResultSet resultSet = statement.executeQuery();
                    boolean exists = resultSet.next() && resultSet.getInt(1) > 0;
                    log.info("User '{}' exist: {}", username, exists);
                    return exists;
            }
        } catch (SQLException e) {
            log.error("Error when checking if a user exists", e);
            throw new RuntimeException("Error during checking if user exists " + e.getMessage(), e);
        }
    }
}

Logi:

2024-01-31T21:08:22.323+01:00  INFO 20876 --- [           main] c.m.S.a.service.RegistrationServiceTest  : Starting RegistrationServiceTest using Java 17.0.6 with PID 20876 (started by champ in C:\Projekty\SkySong)
2024-01-31T21:08:22.323+01:00  INFO 20876 --- [           main] c.m.S.a.service.RegistrationServiceTest  : The following 1 profile is active: "test"
2024-01-31T21:08:22.898+01:00  INFO 20876 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-01-31T21:08:22.970+01:00  INFO 20876 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 64 ms. Found 2 JPA repository interfaces.
2024-01-31T21:08:23.605+01:00  INFO 20876 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 0 (http)
2024-01-31T21:08:23.615+01:00  INFO 20876 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-01-31T21:08:23.615+01:00  INFO 20876 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.11]
2024-01-31T21:08:23.702+01:00  INFO 20876 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-01-31T21:08:23.704+01:00  INFO 20876 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1361 ms
2024-01-31T21:08:23.841+01:00  INFO 20876 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-01-31T21:08:23.903+01:00  INFO 20876 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.6.Final
2024-01-31T21:08:23.906+01:00  INFO 20876 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2024-01-31T21:08:24.039+01:00  INFO 20876 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2024-01-31T21:08:24.164+01:00  INFO 20876 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-01-31T21:08:24.179+01:00  INFO 20876 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-01-31T21:08:24.317+01:00  INFO 20876 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7352418c
2024-01-31T21:08:24.318+01:00  INFO 20876 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(12, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@7bf94e91) replaced previous registration(org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@3fdbb7ee)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(-9, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@32c08610) replaced previous registration(org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@44ec4a38)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(-3, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@755009f2) replaced previous registration(org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@1756a471)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(4003, org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@2190508d) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@70b630d)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(4001, org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@72e1e587) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@5036a286)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(4002, org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@73818435) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@2fce8243)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(2004, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@52fe87e0) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@37393dab)
2024-01-31T21:08:24.379+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(2005, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@4866a755) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@1361e880)
2024-01-31T21:08:24.380+01:00 DEBUG 20876 --- [           main] o.h.t.d.sql.spi.DdlTypeRegistry          : addDescriptor(2011, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@3506bc8b) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@4bac0be5)
2024-01-31T21:08:24.617+01:00  INFO 20876 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2024-01-31T21:08:25.288+01:00  INFO 20876 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2024-01-31T21:08:25.291+01:00  INFO 20876 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-01-31T21:08:25.663+01:00  WARN 20876 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-01-31T21:08:25.990+01:00  INFO 20876 --- [           main] c.m.S.shared.config.SecurityConfig       : SecurityConfig is loaded
2024-01-31T21:08:26.026+01:00  INFO 20876 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@55a7f0af, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@12db2921, org.springframework.security.web.context.SecurityContextHolderFilter@3110bb19, org.springframework.security.web.header.HeaderWriterFilter@4b6abf4e, org.springframework.security.web.authentication.logout.LogoutFilter@7ebc2975, com.mycompany.SkySong.auth.security.JwtAuthenticationFilter@253a8bdb, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@18482805, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@84180e2, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@f6e9c18, org.springframework.security.web.session.SessionManagementFilter@4f1cb802, org.springframework.security.web.access.ExceptionTranslationFilter@310df168, org.springframework.security.web.access.intercept.AuthorizationFilter@879f852]
2024-01-31T21:08:26.247+01:00  INFO 20876 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 60906 (http) with context path ''
2024-01-31T21:08:26.253+01:00  INFO 20876 --- [           main] c.m.S.a.service.RegistrationServiceTest  : Started RegistrationServiceTest in 4.199 seconds (process running for 17.665)
2024-01-31T21:08:26.810+01:00 DEBUG 20876 --- [           main] org.hibernate.SQL                        : select u1_0.id from users u1_0 where u1_0.username=? limit ?
Hibernate: select u1_0.id from users u1_0 where u1_0.username=? limit ?
2024-01-31T21:08:26.822+01:00 DEBUG 20876 --- [           main] org.hibernate.SQL                        : select u1_0.id from users u1_0 where u1_0.email=? limit ?
Hibernate: select u1_0.id from users u1_0 where u1_0.email=? limit ?
2024-01-31T21:08:26.833+01:00 DEBUG 20876 --- [           main] org.hibernate.SQL                        : select r1_0.id,r1_0.name from roles r1_0 where r1_0.name=?
Hibernate: select r1_0.id,r1_0.name from roles r1_0 where r1_0.name=?
2024-01-31T21:08:26.915+01:00  INFO 20876 --- [           main] c.m.S.a.service.RegistrationServiceImpl  : Rejestrowanie użytkownika: Alex
2024-01-31T21:08:26.933+01:00 DEBUG 20876 --- [           main] org.hibernate.SQL                        : insert into users (email,password,username) values (?,?,?)
Hibernate: insert into users (email,password,username) values (?,?,?)
2024-01-31T21:08:26.942+01:00  INFO 20876 --- [           main] c.m.S.a.service.RegistrationServiceImpl  : Zarejestrowano użytkownika
2024-01-31T21:08:26.943+01:00  INFO 20876 --- [           main] c.m.S.testsupport.common.DatabaseHelper  : Execution of the query: SELECT COUNT(*) FROM users WHERE username = ?
2024-01-31T21:08:26.945+01:00  INFO 20876 --- [           main] c.m.S.testsupport.common.DatabaseHelper  : User 'Alex' exist: false

org.opentest4j.AssertionFailedError: 
Expected :true
Actual   :false
<Click to see difference>


	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:31)
	at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:180)
	at com.mycompany.SkySong.auth.service.RegistrationServiceTest.whenValidCredentials_RegisterUser(RegistrationServiceTest.java:39)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

2024-01-31T21:08:26.984+01:00  INFO 20876 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-01-31T21:08:26.986+01:00  INFO 20876 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-01-31T21:08:27.116+01:00  INFO 20876 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code -1

4

Prawdopodobnie problem jest w tym, że @Trasanctional pochodzi z JPA a ty używasz JDBC. Poczytaj o Spring Data JDBC jak chcesz tego używać. Tam masz też chyba taką klasę JdbcUtils, która ma metodę do czyszczenia bazy. A w ogóle używanie @Transactional w testach to kiepski pomysł, bo jak usuniesz tę adnotację z jakiejś metody a w teście zostanie, to test przejdzie mimo, że nie powinien. Lepiej napisać sobie jakiś ręczny cleaner. Ja mam coś takiego u siebie:

@Component
@RequiredArgsConstructor
public class SqlDatabaseCleaner {

    @Autowired
    private final JdbcTemplate jdbcTemplate;

    public void clean() {
        jdbcTemplate.queryForList(
                "SELECT table_name " +
                        "FROM information_schema.tables " +
                        "WHERE table_schema='public'", String.class
        ).forEach(tableName -> jdbcTemplate.execute(
                String.format("TRUNCATE TABLE \"%s\" RESTART IDENTITY CASCADE", tableName)
        ));
    }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1