NanoC
NanoC

Reputation: 61

How test a method with @Transactional annotation

how can i test a method with the @Transaction annotation. I wrote a method which works if i test it when the application is running (manually). But my test for this doesn't work. My goal is to write a test which make sure that my transaction method works when a exception happened, so nothing is stored in the DB. Maybe it is enough to test that the Transaction annotation exist, in the belief that spring works fine. :/ But that is ugly for me.

So my goal is not to test the functionality of springs transaction. My goal is to have a test which make sure that nothing will be stored in the db if a exception happen. Maybe there is a better way to test it.

I drop every time the database before the test is running. The id of the company is every time a another random long.

Setting

application.yml

spring:
 data:
    mongodb:
      uri: mongodb://okAdmin:test@mongo_one:27017,mongo_two:27018,mongo_three:27019/?replicaSet=rs0
      database: "ok"
      auto-index-creation: false

Mongo configuration for spring

@Configuration
public class MongoTransactionConfig extends AbstractMongoClientConfiguration {

    @Value("${spring.data.mongodb.database}")
    private String database;
    @Value("${spring.data.mongodb.uri}")
    private String mongo_uri;

    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) {
        return new MongoTransactionManager(mongoDatabaseFactory);
    }

    @Bean
    public MongoClient mongoClient() {
        return MongoClients.create(mongo_uri);
    }

    @Override
    protected String getDatabaseName() {
        return database;
    }
}

Method

@Slf4j
@Service
public class CompanyService {


    public static final int MIN_NUMBER_OF_BRANCHES = 1;
    private final CompanyRepository companyRepository;

    @Autowired
    public CompanyService(@NonNull CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }


    //  TODO : TEST
    @Transactional // (rollbackFor = {StoreException.class}) => NOT WORKING FOR TESTS/NO CATCHING
    public void addBranch(@NonNull Branch branch){
        log.debug("Service: Add branch to company");
        long companyId = branch.getCompanyId();
        companyRepository.addBranch(branch);
        long branches_number = companyRepository.countBranchesForCompany(companyId);
        if (branches_number == MIN_NUMBER_OF_BRANCHES) {
            companyRepository.setSignupCompleted(companyId, true);
        }

    }
}

Test

@SpringBootTest(properties = {
        "spring.data.mongodb.database=ok_test"
})
//@Transactional
class CompanyServiceMongoTest {
    private final String COLLECTION_BRANCHES = "branches";

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private CompanyService testObject;

    @Autowired
    private CompanyRepository companyRepository;


    @Test
    void addBranch_test_transaction_company_not_saved(){
        // PREPARE
        Branch branchTestDummy = BranchTest.createBranchTestDummy();
        Query branchQuery = new Query();
        branchQuery.addCriteria(Criteria.where("company_id").is(branchTestDummy.getCompanyId()));
        // ACTION
        assertThrows(StoreException.class, ()-> testObject.addBranch(branchTestDummy));
        // CHECK
        boolean exists = mongoTemplate.exists(branchQuery, BranchDao.class, COLLECTION_BRANCHES);
        assertFalse(exists);
    }
}

Test result


org.opentest4j.AssertionFailedError: 
Expected :false
Actual   :true

    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertFalse.assertFalse(AssertFalse.java:40)
    at org.junit.jupiter.api.AssertFalse.assertFalse(AssertFalse.java:35)
    at org.junit.jupiter.api.Assertions.assertFalse(Assertions.java:210)
    at com.localkoop.server.components.company.core.services.CompanyServiceMongoTest.addBranch_test_transaction_company_not_saved(CompanyServiceMongoTest.java:102)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
    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:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    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.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1507)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1507)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

Run console output

2020-06-17 11:12:34.635  INFO 182967 --- [mongo_two:27017] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1426609, serverValue:1511263}] to mongo_two:27017
2020-06-17 11:12:34.635 DEBUG 182967 --- [mongo_two:27017] org.mongodb.driver.cluster               : Checking status of mongo_two:27017
2020-06-17 11:12:34.635 DEBUG 182967 --- [mongo_two:27017] org.mongodb.driver.protocol.command      : Sending command '{"ismaster": 1, "$db": "admin", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1592385154, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "2Qn1NUq/OPeFI3jd2aPIoloM/x0=", "subType": "00"}}, "keyId": 6837089852169650179}}}' with request id 2853624 to database admin on connection [connectionId{localValue:1426609, serverValue:1511263}] to server mongo_two:27017
2020-06-17 11:12:34.635 DEBUG 182967 --- [mongo_two:27017] org.mongodb.driver.protocol.command      : Execution of command with request id 2853624 completed successfully in 0.32 ms on connection [connectionId{localValue:1426609, serverValue:1511263}] to server mongo_two:27017
2020-06-17 11:12:34.636  INFO 182967 --- [mongo_two:27017] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=mongo_two:27017, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=403948, setName='rs0', canonicalAddress=mongo_one:27017, hosts=[mongo_one:27017], passives=[mongo_two:27017, mongo_three:27017], arbiters=[], primary='mongo_one:27017', tagSet=TagSet{[]}, electionId=7fffffff0000000000000009, setVersion=1, lastWriteDate=Wed Jun 17 11:12:34 CEST 2020, lastUpdateTimeNanos=9570708142930}
2020-06-17 11:12:34.636  INFO 182967 --- [mongo_two:27017] org.mongodb.driver.cluster               : Adding discovered server mongo_three:27017 to client view of cluster
2020-06-17 11:12:34.636  INFO 182967 --- [mongo_two:27017] org.mongodb.driver.cluster               : Canonical address mongo_one:27017 does not match server address.  Removing mongo_two:27017 from client view of cluster
2020-06-17 11:12:34.636 DEBUG 182967 --- [mongo_two:27017] org.mongodb.driver.cluster               : Updating cluster description to  {type=REPLICA_SET, servers=[{address=mongo_one:27017, type=REPLICA_SET_PRIMARY, roundTripTime=0.6 ms, state=CONNECTED}, {address=mongo_three:27017, type=UNKNOWN, state=CONNECTING}]
2020-06-17 11:12:34.636 DEBUG 182967 --- [mongo_two:27017] org.mongodb.driver.connection            : Closing connection connectionId{localValue:1426609, serverValue:1511263}
2020-06-17 11:12:34.637  INFO 182967 --- [ngo_three:27017] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1426610, serverValue:1511264}] to mongo_three:27017
2020-06-17 11:12:34.637 DEBUG 182967 --- [ngo_three:27017] org.mongodb.driver.cluster               : Checking status of mongo_three:27017
2020-06-17 11:12:34.637 DEBUG 182967 --- [ngo_three:27017] org.mongodb.driver.protocol.command      : Sending command '{"ismaster": 1, "$db": "admin", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1592385154, "i": 1}}, "signature": {"hash": {"$binary": {"base64": "2Qn1NUq/OPeFI3jd2aPIoloM/x0=", "subType": "00"}}, "keyId": 6837089852169650179}}}' with request id 2853626 to database admin on connection [connectionId{localValue:1426610, serverValue:1511264}] to server mongo_three:27017
2020-06-17 11:12:34.637 DEBUG 182967 --- [ngo_three:27017] org.mongodb.driver.protocol.command      : Execution of command with request id 2853626 completed successfully in 0.36 ms on connection [connectionId{localValue:1426610, serverValue:1511264}] to server mongo_three:27017
2020-06-17 11:12:34.637  INFO 182967 --- [ngo_three:27017] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=mongo_three:27017, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=457039, setName='rs0', canonicalAddress=mongo_one:27017, hosts=[mongo_one:27017], passives=[mongo_two:27017, mongo_three:27017], arbiters=[], primary='mongo_one:27017', tagSet=TagSet{[]}, electionId=7fffffff0000000000000009, setVersion=1, lastWriteDate=Wed Jun 17 11:12:34 CEST 2020, lastUpdateTimeNanos=9570709659086}
2020-06-17 11:12:34.637  INFO 182967 --- [ngo_three:27017] org.mongodb.driver.cluster               : Adding discovered server mongo_two:27017 to client view of cluster
2020-06-17 11:12:34.637  INFO 182967 --- [ngo_three:27017] org.mongodb.driver.cluster               : Canonical address mongo_one:27017 does not match server address.  Removing mongo_three:27017 from client view of cluster
2020-06-17 11:12:34.637 DEBUG 182967 --- [ngo_three:27017] org.mongodb.driver.cluster               : Updating cluster description to  {type=REPLICA_SET, servers=[{address=mongo_one:27017, type=REPLICA_SET_PRIMARY, roundTripTime=0.6 ms, state=CONNECTED}, {address=mongo_two:27017, type=UNKNOWN, state=CONNECTING}]
2020-06-17 11:12:34.637 DEBUG 182967 --- [ngo_three:27017] org.mongodb.driver.connection            : Closing connection connectionId{localValue:1426610, serverValue:1511264}

THANK YOU FOR YOUR HELP

Upvotes: 6

Views: 8226

Answers (2)

NanoC
NanoC

Reputation: 61

So i found a solution which works for me. I annotated my test class with

@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class, includeFilters = @ComponentScan.Filter(Component.class))
@ActiveProfiles("test")
class CompanyServiceMongoTest {}

I don't know why the annotation @SpringBootTest doesn't work. I thought that @SpringBootTest is a "super context" of @DataMongoTest, but my tests doesn't work with @SpringBootTest annotation. Be aware to use @Transactional as annotation in the test class layer. It doesn't work fine with the transaction mechanism in the service.

Upvotes: 0

Marek Puchalski
Marek Puchalski

Reputation: 3659

Personally I would not write a unit test to test that @Transactional works correctly. My reason is, the environment for the unit test execution is not the same as the environment the bean works in. I have seen too many cases where the unit tests work fine, but the actual system does not.

You can rather try to write an integration tests that will run against an instance of the system (send a request, trigger the functionality, look into the DB to see whether it is what you expect to see). Makes more sense to me, but may also be more time consuming to do.

Upvotes: 3

Related Questions