Home > Net >  @Qualifier not working in unit test, while injecting in constructor
@Qualifier not working in unit test, while injecting in constructor

Time:07-16

I have a application whose unit tests work correctly. I am trying to move the application to use dependency injection instead of using @AutoWired annotation.

After I make the change, some unit tests fail randomly(No changes have been made to the tests, just the dependency injection part of the application has been changed). I am assuming that the system is unable to create the AmazonS3 client correctly when the dependencies are injected.

See screenshot below, I am trying to create a s3ClientAlphaBeta instance but the application is creating a s3ClientGammaProd instance. enter image description here

What could I be missing?

Abreviated code:

S3Accessor.java:

    @Component
    public class S3Accessor {
        private AmazonS3 s3ClientAlphaBeta;
        private AmazonS3 s3ClientGammaProd;
        @Inject
        public S3Accessor(@Qualifier("s3ClientAlphaBeta") AmazonS3 s3ClientAlphaBeta,
                @Qualifier("s3ClientGammaProd") AmazonS3 s3ClientGammaProd) {
            this.s3ClientAlphaBeta = s3ClientAlphaBeta;
            this.s3ClientGammaProd = s3ClientGammaProd;
        }
public String getHtmlContent(final String deviceType, final String s3Key, final String region) {
        final String filePath = generateFilePath(deviceType.toLowerCase(Locale.ROOT), s3Key);
        AmazonS3 amazonS3 = s3ClientAlphaBeta;
        final String regionSpecificLegoBucket = S3BUCKET_NAME_BETA;
        final AmazonS3 regionSpecificAmazonS3 = amazonS3;
        return this.getHtmlContentFromS3(regionSpecificLegoBucket, filePath, regionSpecificAmazonS3);
    }

    private String getHtmlContentFromS3(final String bucketName, final String filePath, final AmazonS3 amazonS3) {
        String s3HtmlContent = amazonS3.getObjectAsString(bucketName, filePath);
        return s3HtmlContent;
    }

S3AccessorTest.java:

public class S3AccessorTest {
@InjectMocks
private S3Accessor s3Accessor;
@Spy
@Qualifier("s3ClientAlphaBeta")
private AmazonS3 s3ClientAlphaBeta;
@Spy
@Qualifier("s3ClientGammaProd")
private AmazonS3 s3ClientGammaProd;
private static final String TEST_VALID_STRING_DESKTOP = "<html><body>Some Content For DESKTOP</body></html>";
private static final String TEST_VALID_WRAPPED_STRING_DESKTOP =
        PAINTERS_LEGO_OPENING_DIV   TEST_VALID_STRING_DESKTOP   PAINTERS_LEGO_CLOSING_DIV;
private static final String TEST_VALID_S3_KEY = "/test/.1/main";
private static final String SLASH = "/";
static {
    AppConfig.destroy();
    AppConfig.initialize("TestApp",
            "TestGroup",
            new String[] { "--root=configuration/", "--domain=test", "--realm=Ramu" });
}
@BeforeEach
public void setup() {
    MockitoAnnotations.openMocks(this);
    s3Accessor.setup();
}
@Test
public void getHtmlContentForDesktop() {
    // Arrange
    when(s3ClientAlphaBeta.getObjectAsString(anyString(), eq(TEST_VALID_S3_KEY   SLASH   DESKTOP_VIEW)))
            .thenReturn(TEST_VALID_STRING_DESKTOP);
    // Act
    final String outputString = s3Accessor.getHtmlContent(DESKTOP, TEST_VALID_S3_KEY, US_ALPHA_BETA_REGION);
    // Assert
    assertEquals(TEST_VALID_WRAPPED_STRING_DESKTOP, outputString);
}

CustomRootConfig.java:

    @Configuration
@ComponentScan("com.service.controller")
@Import({SmokeTestBeanConfiguration.class })
public class CustomRootConfig {
    private static final String NA_S3_SESSION_NAME = "S3Content";
    private static final int S3_SESSION_REFRESH_TIME = 3600;
    private static final String S3BUCKET_ARN_BETA = "arn:aws:iam::471963992020:role/ProdAccessForAccount";
    private static final String S3BUCKET_ARN_PROD = "arn:aws:iam::568948772290:role/ProdAccessForAccount";

    @Bean(name = "s3ClientAlphaBeta")
    @Singleton
    public AmazonS3 s3ClientAlphaBeta() {
        return AmazonS3ClientBuilder.standard()
                .withRegion(Regions.valueOf(US_ALPHA_BETA_REGION))
                .withCredentials(new STSAssumeRoleSessionCredentialsProvider
                        .Builder(S3BUCKET_ARN_BETA, NA_S3_SESSION_NAME)
                        .withRoleSessionDurationSeconds(S3_SESSION_REFRESH_TIME).build())
                .build();
    }

    @Bean(name = "s3ClientGammaProd")
    @Singleton
    public AmazonS3 s3ClientGammaProd() {
        return AmazonS3ClientBuilder.standard()
                .withRegion(Regions.valueOf(US_GAMMA_PROD_REGION))
                .withCredentials(new STSAssumeRoleSessionCredentialsProvider
                        .Builder(S3BUCKET_ARN_PROD, NA_S3_SESSION_NAME)
                        .withRoleSessionDurationSeconds(S3_SESSION_REFRESH_TIME).build())
                .build();
    }

CodePudding user response:

I don't think Mockito actually recognizes the @Qualifier annotation from Spring. The reason it worked before is that Mockito was doing property injection, as there was no other way to do it.

After changes, with the constructor in place, Mockito switched from property injection to constructor injection. What's important is that both s3ClientAlphaBeta and s3ClientGammaProd are of the same type (AmazonS3). Apparently, with constructor injection, Mockito isn't doing so well to differentiate the parameters by its name. Possible reason is that the names of the parameters aren't preserved after compilation, so there's no way for Mockito to match them. Instead, it uses the first mock (spy) it finds with a matching type (either s3ClientAlphaBeta or s3ClientGammaProd) and uses it for both parameters to construct an instance of S3Accessor.

To avoid your issue and gain more control over how the tested object is being created, I'd suggest abandoning usage of @InjectMocks and using the constructor directly in the tested class i.e. doing:

s3Accessor = new S3Accessor(s3ClientAlphaBeta, s3ClientGammaProd);

somewhere below MockitoAnnotations.openMocks(this).

You can find more information on how @InjectMocks annotation works here.

  • Related