luchob / softuni-music-db

Our workshop project for the course Spring Advanced 2021
3 stars 11 forks source link

Проблеми с добавянето на MultipartFile в JUnit тестовете #19

Closed DDDimitrovvv closed 3 years ago

DDDimitrovvv commented 3 years ago

Добър вечер, г-н Балев! Надявам се да сте вече по-добре! Опасявам се, че не мога да продължа с тестовете за добавянето на продукт към базата, поради факта, че имплементацията на MultipartFile не ми позволява да я превърна в String. Идеята ми е да използвам тази функционалност, както Вие показахте на демото за Cloudinary. Стигнах до добавянето на файла като параметър, но това не се получава, тъй като се изисква String. Опитах с различни имплементации, според няколкото теми в Stack Overflow, но без успех. Моля за съвет от Ваша страна.

Ето линк към репото ми: https://github.com/DDDimitrovvv/DIMO

luchob commented 3 years ago

А, не, не, господин Димитров ;-) Мисля, че MultipartFile няма нужда да се превръща в стринг. Нега направим така. Първо, това е чудесно и решава 50% от проблема:

@MockBean
    CloudinaryService mockCloudinaryService;

Второ, нека да ползваме MockMultipartFile, създаден е имено с тази цел. Например:

 MockMultipartFile mockImgFile
            = new MockMultipartFile(
            "imageUrl",
            "hello.png",
            MediaType.TEXT_PLAIN_VALUE,
            "Hello, World!".getBytes()
        );

Тук е важен аргумента imageUrl, трябва да е същото име като в байндинг модела. Накрая, да използваме и самото mockMvc по правилен начин:

        mockMvc.perform(
            MockMvcRequestBuilders.multipart(
                PRODUCT_CONTROLLER_PREFIX + "/add")
                .file(mockImgFile)
                .param("brand", "HK")
                .param("model", "AVR270")
                .param("color", "black")
                .param("manufactureDate", "2019-01-09")
                .param("price", "100")
                .param("warranty", "12")
                .param("details", "Your AVR includes Dolby Pro Logic IIz decoding, which uses the AVR’s Assigned Amp...")
                .param("categoryName", "Receivers")
                .with(csrf())).
                andExpect(status().is3xxRedirection());

Тук е хубаво да се обърне внимание на multipart (не се ползва post!) и на file. А ето го и целия зелен тест:

    @Test
    @WithMockUser(value = "test@abv.bg", roles = {"USER"})
    void addProduct() throws Exception {

        MockMultipartFile mockImgFile
            = new MockMultipartFile(
            "imageUrl",
            "hello.png",
            MediaType.TEXT_PLAIN_VALUE,
            "Hello, World!".getBytes()
        );

        mockMvc.perform(
            MockMvcRequestBuilders.multipart(
                PRODUCT_CONTROLLER_PREFIX + "/add")
                .file(mockImgFile)
                .param("brand", "HK")
                .param("model", "AVR270")
                .param("color", "black")
                .param("manufactureDate", "2019-01-09")
                .param("price", "100")
                .param("warranty", "12")
                .param("details", "Your AVR includes Dolby Pro Logic IIz decoding, which uses the AVR’s Assigned Amp...")
                .param("categoryName", "Receivers")
                .with(csrf())).
                andExpect(status().is3xxRedirection());

        Assertions.assertEquals(2, productRepository.count());
    }

Надявам се, това да е полезно! И да - най-сетне вече съм по-добре!

Поздрави, Л.

DDDimitrovvv commented 3 years ago

Добро утро! Много се радвам, че сте по-добре! :)

Благодаря много за помощта. Да, получи се, като при стартиране на само този тест и след добавянето на променливите за cloudinary всичко минава гладко и тестът е зелен. Но след като направя глобален тест на контролера с двата метода, получавам грешка, която намирам за доста странна:

2021-03-29 10:41:26.374 WARN 16380 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23505, SQLState: 23505 2021-03-29 10:41:26.374 ERROR 16380 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Unique index or primary key violation: "PUBLIC.UK_R43AF9AP4EDM43MMTQ01ODDJ6_INDEX_4 ON PUBLIC.USERS(USERNAME) VALUES 3"; SQL statement: insert into users (id, fullname, password, username) values (null, ?, ?, ?) [23505-200]

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PUBLIC.UK_R43AF9AP4EDM43MMTQ01ODDJ6_INDEX_4 ON PUBLIC.USERS(USERNAME) VALUES 3"; SQL statement: insert into users (id, fullname, password, username) values (null, ?, ?, ?) [23505-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

Минах през дебъг режим и установих, че когато се създава ентитито за втория тест (addProduct()), буквално след реда "userEntity = userRepository.save(userEntity);" ми изхвърля грешката.

Комитнал съм промените. Когато имате възможност може да хвърлите по едно очо. :D

luchob commented 3 years ago

Привет! Ами не е странно. Нека видим user ентитито:

    @Column(name = "username", nullable = false, unique = true)
    private String username;

Т.е. username е уникално.

Имаш и това нещо:

    @BeforeEach
    public void setUp() throws IOException {
         this.init();
    }

В превод - създай юзър Х преди всеки тест. Обаче, юзъра от предния тест си стои в ДБ, не изчезва от самосебе си. Тук ти предлагам няколко варианта.

  1. Метод анотиран с @AfterEach в който триеш пораженията от предния тест
  2. Анотиране на тестовете с @DirtiesContext (малко мръсничко, но можеш пък да проваш с учебна цел)
  3. Логика в init() която да не допуска създаването на 1 юзър n-на брой пъти.

Поздрави, Л.

DDDimitrovvv commented 3 years ago

Чудесно! Получи се! Използвах първия посочен от Вас вариант. Много Ви благодаря за съдействието! Бързо пълно възстановяване!

Поздрави, Д.Димитров