SpringBoot事務對TestRestTemplate的影響

前言

在本週進行後臺用戶登陸單元測試的過程當中,因爲與以前的結構產生了較大的變化,所以出現了不少的問題,在衆多問題中,因爲事務出現的問題可謂是觸及到知識盲區,甚至老師說了問題出在事務上,本身也仍是處於很懵的狀態,所以特地瞭解了一下事務對該測試的影響,力求下次再出現問題時,能知道問題的根本。html

問題

在本週對後臺登陸方法進行單元測試的過程當中,出現了斷言錯誤:java

ERROR:
image.pngweb

測試方法:spring

@BeforeEach
public void addCurrentLoginUser() {
        this.username = "188" + String.valueOf(CommonService.getRandomNumberLongs(10000000, 99999999));
        this.password = RandomString.make(40);
        this.user = new User();
        this.user.setUsername(this.username);
        this.user.setPassword(this.password);
        this.userRepository.save(this.user);
    }
    
 @Test
    void getCurrentLoginUser() {
        logger.debug("初始化基礎數據");
        HttpHeaders headers;
        HttpEntity<Void> entity;
        ResponseEntity<Void> response;

        logger.debug("1: 測試用戶名密碼正確");
        headers = new HttpHeaders();
        entity = new HttpEntity<>(null, headers);
        response = this.restTemplate
                .withBasicAuth(this.username, this.password)
                .exchange(CONFIG_LOGIN, HttpMethod.GET, entity, Void.class);

        logger.debug("斷言: 狀態碼爲200");
        assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());
}

執行方法:數據庫

@Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.debug("根據用戶名查詢用戶");
        logger.debug(username);
        User user = this.userRepository.findByUsername(username).orElseThrow(() -> new ObjectNotFoundException("user實體未找到"));
        if (user == null) {
            logger.error("用戶名不存在");
            throw new UsernameNotFoundException("用戶名不存在");
        }

        logger.debug("構造用戶");
        return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
    }

測試原理:json

image.png

一時間竟不知從何下手,而後只能對方法打斷點進行DEBUG測試:
因爲在執行登陸方法時調用了loadUserByUsername()方法, 所以對該方法進行DEBUG測試:
image.pngsegmentfault

結果:api

image.png

在測試方法中執行findByUsername()方法, 進行DEBUG測試:
image.pngcookie

結果:app

image.png

newUser存在, 說明findByUsername()方法沒有問題, 一時半會竟沒有思路,只好求助於潘老師, 最後老師給出的結論是:

image.png

解決

因爲以前的測試類繼承於ControllerTest,而ControllerTest類上有@Transactional註解:
image.png

image.png

在刪除了對ControllerTest的繼承後,問題得以解決:

image.png

爲何

爲何事務會對測試產生影響呢, 下面咱們來分析一下, 關於事務的概念與特性,上篇文章《@Transactional 事務註解》已經介紹的很清楚了,本次咱們來深刻一下:

image.png
咱們分別在有事務和沒有事務的狀況下進行單元測試, 並觀察數據庫的數據變化:
有事務:
image.png

沒有事務:
image.png

原本Blog寫到這裏就中止了,由於發現本身仍是沒理解老師的意思,只是看明白了,可是內在的機制仍是不清楚,後來彙報時老師又講了一遍, 茅塞頓開,發現本身仍是瞭解的太少了。

TestRestTemplate

(因爲翻譯可能會曲解官方的意思, 所以此處直接引用官方的說明)
對於TestRestTemplate, Spring官方文檔的說明是:

Convenient alternative of RestTemplate that is suitable for integration tests. They are fault tolerant, and optionally can carry Basic authentication headers. If Apache Http Client 4.3.2 or better is available (recommended) it will be used as the client, and by default configured to ignore cookies and redirects.

Note: To prevent injection problems this class intentionally does not extend RestTemplate. If you need access to the underlying RestTemplate use getRestTemplate().

If you are using the @SpringBootTest annotation with an embedded server, a TestRestTemplate is automatically available and can be @Autowired into your test. If you need customizations (for example to adding additional message converters) use a RestTemplateBuilder @Bean.

IDEA控制檯信息

2021-01-03 23:19:46.130  INFO 1301 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@561d88ee testClass = UserControllerTest, testInstance = club.yunzhi.questionnaire.Controller.UserControllerTest@2c34402, testMethod = getUserById@UserControllerTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5f883d90 testClass = UserControllerTest, locations = '{}', classes = '{class club.yunzhi.questionnaire.QuestionnaireApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@7e58f697 key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1165b38, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@3b69e7d1, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@5ac1576e, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@79079097, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@710f4dc7, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@f238e4f]; rollback [true]
2021-01-03 23:19:46.370  INFO 1301 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@561d88ee testClass = UserControllerTest, testInstance = club.yunzhi.questionnaire.Controller.UserControllerTest@2c34402, testMethod = getUserById@UserControllerTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5f883d90 testClass = UserControllerTest, locations = '{}', classes = '{class club.yunzhi.questionnaire.QuestionnaireApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@7e58f697 key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1165b38, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@3b69e7d1, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@5ac1576e, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@79079097, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@710f4dc7, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]

以上是一段IDEA在測試時控制檯的信息,接觸IDEA已經一年有餘,但當老師問起這些信息都表示什麼時,發現本身根本不瞭解這些信息,在老師的講解下,發現這些信息真的挺有用的, 以上信息分別爲:

年-月-日 時:分:秒.毫秒 日誌級別 進程號 --- [線程號] 測試信息等

MySQL事務

MYSQL 事務處理主要有兩種方法:

一、用 BEGIN, ROLLBACK, COMMIT來實現

  • BEGIN 開始一個事務
  • ROLLBACK 事務回滾
  • COMMIT 事務確認

二、直接用 SET 來改變 MySQL 的自動提交模式:

  • SET AUTOCOMMIT=0 禁止自動提交
  • SET AUTOCOMMIT=1 開啓自動提交

這樣咱們能夠了解到, 在事務不進行commit操做的狀況下, 數據庫是不會進行存取數據的操做的, 若是事務提交,相應的語句纔會執行。

MockMVC測試及TestTemplate測試的區別

在進行MockMVC測試時, 因爲使用的是Mock方法, 這使得Mock測試僅使用一個線程就能夠完成測試:

image.png

可是TestTemplate在測試時, 須要啓動兩個線程, 一個爲SpringBootTest, 另外一個爲SpringBoot應用, 測試機制以下:
image.png

因爲線程的資源不共享, 因此在Test線程事務未提交的狀況下, Application線程的事務是查不到任何數據的。

仿真測試

首先開啓TestTemplate線程的事務:
image.png

而後向role表插入一條數據:

INSERT INTO role(id,deleted, `name`) VALUES (NULL,TRUE,'張三')

而後咱們在當前事務執行查詢操做:

SELECT * FROM role;

RESULT:
image.png

接着咱們開啓SpringBootApplication的線程事務:

image.png

而後執行查詢操做:

SELECT * FROM role;

RESULT:
image.png

接着咱們提交TestTemplate線程的事務:

COMMIT

而後再次在SpringBootApplication線程的事務中執行查詢操做:

image.png

代碼以下:

# TestTemplate 線程事務
BEGIN
INSERT INTO role(id,deleted, `name`) VALUES (NULL,TRUE,'張三');
SELECT * FROM role;
COMMIT
#SpringBootApplication 線程事務
BEGIN 
SELECT * FROM role

總結

以前覺得控制檯的信息是沒有做用的, 也就沒有去關注過, 可是通過這次出現問題發現控制檯的信息十分重要, 想一想本身使用了一年多IDEA, 卻沒有了解控制檯信息, 想來也是十分慚愧, 本次也對事務有了新的瞭解, 相信之後會更深刻吧。紙上得來終覺淺,絕知此事要躬行。

相關文章
相關標籤/搜索