Отмена зависимости в тесте Micronaut

Я тестирую класс Micronaut, в который введен bean-компонент. В моем тесте я использую класс @MockBean, чтобы его переопределить. Однако похоже, что Micronaut все еще внедряет настоящую зависимость.

@MicronautTest
public class ClassUnderTestTest {

    @Inject ClassUnderTest classUnderTest;

    @Test
    public void test() {

    }

    @MockBean
    Dependency dependency() {
        return mock(Dependency.class);
    }

}

Я загрузил на Github минимальный репро: https://github.com/crummy/micronaut-test-dependencies. Настоящая зависимость вызывает исключение, и тест тоже. Я бы не ожидал этого из-за моего @MockBean.

Если я изменю аннотацию на @MockBean(Dependency.class), я получу эту ошибку: Message: No bean of type [di.failure.example.Dependency] exists. Мне это кажется еще более запутанным - теперь он не разрешает мою настоящую или фиктивную зависимость?


person Malcolm Crum    schedule 03.11.2018    source источник


Ответы (1)


Внедрение фиктивного bean-компонента с аннотацией @MockBean работает, если ваша зависимость в ClassUnderTest представлена ​​интерфейсом. Допустим, Dependency - это простой интерфейс вроде:

package di.failure.example;

public interface Dependency {
    void run();
}

Ваше приложение может предоставлять реализацию этого интерфейса под названием DependencyImpl:

package di.failure.example;

import javax.inject.Singleton;

@Singleton
public class DependencyImpl implements Dependency {
    @Override
    public void run() {
        throw new RuntimeException("I don't want this to load!");
    }
}

Теперь для целей тестирования вы можете определить макет, который заменяет DependencyImpl:

package di.failure.example;

import io.micronaut.test.annotation.MicronautTest;
import io.micronaut.test.annotation.MockBean;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

import static org.mockito.Mockito.mock;

@MicronautTest
public class ClassUnderTestTest {

    @Inject
    ClassUnderTest classUnderTest;

    @Test
    public void test() {
        classUnderTest.run();
    }

    @MockBean(DependencyImpl.class)
    public Dependency dependency() {
        return mock(Dependency.class);
    }

}

Этот тест выполняется, и макет, возвращаемый методом dependency(), используется вместо DependencyImpl.

Использование аннотации @Replaces

Как упоминалось в разделе комментариев Серджио, вы можете заменить зависимость bean-компонента на основе классов, используя _ 12_ аннотация. Рассмотрим следующий пример:

package di.failure.example;

import io.micronaut.context.annotation.Replaces;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;
import javax.inject.Singleton;

@MicronautTest
public class ClassUnderTestTest {

    @Inject
    ClassUnderTest classUnderTest;

    @Test
    public void test() {
        classUnderTest.run();
    }

    @Replaces(Dependency.class)
    @Singleton
    public static class MockDependency extends Dependency {

        public MockDependency() {
            System.out.println("MockDependency.<init>");
        }

        @Override
        void run() {
            System.out.println("Does not throw any exception...");
        }
    }
}

В этом примере мы определили класс MockDependency и инструктируем механизм DI Micronaut заменить bean-компонент Dependency на MockDependency. Однако есть одна важная вещь, о которой нам нужно помнить - поскольку наш MockDependency расширяет Dependency класс, вызывается родительская конструкция. Пример, который вы показали в вопросе, не будет работать в этом случае, потому что Dependency.<init> выдает RuntimeException и тест не проходит. В этом модифицированном примере я использовал такой класс:

package di.failure.example;

import javax.inject.Singleton;

@Singleton
public class Dependency {

    public Dependency() {
        System.out.println("Dependency.<init>");
    }

    void run() {
        throw new RuntimeException("I don't want this to load!");
    }
}

Когда я запускаю тест, он проходит, и я вижу следующий вывод консоли:

Dependency.<init>
MockDependency.<init>
Does not throw any exception...

Основное отличие от @MockBean в том, что в случае @Replaces вы используете конкретный объект класса. В качестве обходного пути (если нам действительно нужен объект-макет Mockito) является создание макета внутри и делегирование вызовов этому объекту, примерно так:

@Replaces(Dependency.class)
@Singleton
public class MockDependency extends Dependency {

    private final Dependency delegate;

    public MockDependency() {
        this.delegate = mock(Dependency.class);
    }

    @Override
    void run() {
        delegate.run();
    }
}
person Szymon Stepniak    schedule 03.11.2018
comment
Хорошо, это позор (я предпочитаю избегать интерфейсов, если они у меня нет практической необходимости). Но я думаю, что это один из них. - person Malcolm Crum; 04.11.2018
comment
Если вам нужно заменить класс, вы всегда можете создать bean-компонент в пути к тестовым классам и использовать @Replaces - person Sergio del Amo; 04.11.2018
comment
Спасибо @SergiodelAmo за ценный вклад! Я обновил ответ с помощью варианта использования аннотации @Replaces. - person Szymon Stepniak; 04.11.2018
comment
Что я хочу переопределить для значения свойства, а не для всего компонента? Типичными примерами являются базы данных в оперативной памяти. - person Abhijit Sarkar; 05.04.2019
comment
Что я хочу переопределить для значения свойства, а не для всего компонента? - вы можете сделать это с помощью файлов конфигурации для конкретной среды, например src/main/resources/application-test.yml. Тесты, отмеченные знаком @MicronautTest, также могут предоставлять свои собственные значения конфигурации. - person Jeff Scott Brown; 01.06.2021