Overriding a dependency in a Micronaut test

10,028

Injecting mock bean with @MockBean annotation works if your dependency in ClassUnderTest is represented by interface. Let's say Dependency is a simple interface like:

package di.failure.example;

public interface Dependency {
    void run();
}

Your application may provide an implementation for this interface called 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!");
    }
}

Now, for test purpose you can define a mock that replaces 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);
    }

}

This test executes and the mock returned by dependency() method is used in place of DependencyImpl.

Using @Replaces annotation

As Sergio mentioned in the comments section you can replace class based bean dependency using @Replaces annotation. Consider following example:

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...");
        }
    }
}

In this example we have defined a class MockDependency and we instruct Micronaut's DI mechanism to replace Dependency bean with MockDependency. However, there is one important thing we need to remember about - because our MockDependency extends Dependency class, parent construct gets invoked. The example you have shown in the question won't work in this case, because Dependency.<init> throws RuntimeException and the test fails. In this modified example I have used class like this one:

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!");
    }
}

When I run the test it passes and I see following console output:

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

The main difference comparing to @MockBean is that in case of @Replaces you are using a concrete class object. As a workaround (if we really need a Mockito mock object) is to create a mock internally and delegate calls to this object, something like this:

@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();
    }
}
Share:
10,028
Malcolm Crum
Author by

Malcolm Crum

Updated on June 14, 2022

Comments

  • Malcolm Crum
    Malcolm Crum almost 2 years

    I'm testing a Micronaut class that has a bean injected into it. In my test I provide a @MockBean class to override it. However, it seems Micronaut still injects the real dependency.

    @MicronautTest
    public class ClassUnderTestTest {
    
        @Inject ClassUnderTest classUnderTest;
    
        @Test
        public void test() {
    
        }
    
        @MockBean
        Dependency dependency() {
            return mock(Dependency.class);
        }
    
    }
    

    I uploaded a minimum repro to Github: https://github.com/crummy/micronaut-test-dependencies . The real dependency throws an exception, and the test does too. I would not have expected this to happen because of my @MockBean.

    If I change the annotation to be @MockBean(Dependency.class) then I get this error: Message: No bean of type [di.failure.example.Dependency] exists. This seems even more confusing to me - now it doesn't resolve my real or my mock dependency?

  • Malcolm Crum
    Malcolm Crum over 5 years
    OK, that's a shame (I prefer to avoid interfaces unless I have a practical need for them.) But I guess this is one.
  • Sergio del Amo
    Sergio del Amo over 5 years
    If you need to replace a class you can always create a bean in the test classpath and use @Replaces
  • Szymon Stepniak
    Szymon Stepniak over 5 years
    Thanks @SergiodelAmo for the valuable input! I have updated the answer with @Replaces annotation use case.
  • Abhijit Sarkar
    Abhijit Sarkar about 5 years
    What is I want to override a property value, not a whole bean? Typical examples are in-memory databases.
  • Jeff Scott Brown
    Jeff Scott Brown almost 3 years
    "What is I want to override a property value, not a whole bean?" - you can do that with environment specific config files like src/main/resources/application-test.yml. Tests marked with @MicronautTest can also provide their own config values.