Jane Hayes
Jane Hayes

Reputation: 115

Issue getting Reflection to work in Unit test

I'm trying to run a unit test case but am having trouble getting reflection to work. My testSubject uses a supplier class to get a map. The supplier class has a modulusChecker autowired in that I'm trying to set with reflection. However, whenever I run the test the doubleAlterneteModulusChecker is set to null?

package uk.co.cdl.account.bankdetailsvalidation.api.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;
import org.springframework.test.util.ReflectionTestUtils;

import uk.co.cdl.account.bankdetailsvalidation.model.BankDetailsValidationModel;
import uk.co.cdl.account.bankdetailsvalidation.moduluschecker.DoubleAlternateModulusChecker;
import uk.co.cdl.account.bankdetailsvalidation.supplier.ModulusCheckerMapSupplier;
import uk.co.cdl.account.bankdetailsvalidation.supplier.ModulusWeightTableSupplier;
import uk.co.cdl.account.bankdetailsvalidation.supplier.SortCodeSubstitutionTableSupplier;

@ExtendWith(MockitoExtension.class)
@DisplayName("CDLBankDetailsValidator Unit Tests")
class CDLBankDetailsValidatorTest {

    @Spy
    private DoubleAlternateModulusChecker doubleAlternateModulusChecker;

    @Spy
    private ModulusCheckerMapSupplier modulusCheckerMapSupplier;

    @Spy
    private ValidationExceptionRuleFactory validationExceptionRuleFactory;

    @Spy
    private SortCodeSubstitutionTableSupplier sortCodeSubstitutionTableSupplier;

    @Spy
    private ModulusWeightTableSupplier mockModulusTableSupplier;

    @InjectMocks
    private BankDetailsValidator testSubject;

    @DisplayName("validate() returns a BankDetailsValidationModel with the correct values for a given bank account number / sort code")
    @ParameterizedTest(name = "{3}" )
    @MethodSource("bankAccountDetailsTestData")
    void testCDLValidatorWithTestData(String sortCode, String accountNumber, BankDetailsValidationModel expected, String testDescription){
        ReflectionTestUtils.setField(modulusCheckerMapSupplier, "doubleAlternateModulusChecker", doubleAlternateModulusChecker);

        //then
        assertEquals(expected, testSubject.validate(accountNumber, sortCode));
    }

    @DisplayName("Logger captures the correct debug messages")
    @ParameterizedTest(name="Logged: {2}")
    @MethodSource("loggerTestData")
    void testLoggerCapturesDebugMessages(String sortCode, String accountNumber, String expected, int index){
        //Given
        ListAppender<ILoggingEvent> listAppender = getListAppender();

        //When
        testSubject.validate(accountNumber, sortCode);
        String actual = listAppender.list.get(index).toString();

        //Then
        assertEquals(expected, actual);
    }

    private ListAppender<ILoggingEvent> getListAppender(){
        Logger logger = (Logger) LoggerFactory.getLogger(CDLBankDetailsValidator.class);
        ListAppender<ILoggingEvent>listAppender = new ListAppender<>();
        listAppender.start();
        logger.addAppender(listAppender);
        return listAppender;
    }
}

@Component
public class ModulusCheckerMapSupplier implements Supplier<Map> {

    @Autowired
    private DoubleAlternateModulusChecker doubleAlternateModulusChecker;

    @Override
    public Map get() {
        Map<String, StandardModulusChecker> modulusChecksByAlgorithmName = new HashMap<>();
        modulusChecksByAlgorithmName.put(DBLAL, doubleAlternateModulusChecker);
        modulusChecksByAlgorithmName.put(MOD10, new StandardModulusChecker(10));
        modulusChecksByAlgorithmName.put(MOD11, new StandardModulusChecker(11));
        return modulusChecksByAlgorithmName;
    }
}

@Component
public class DoubleAlternateModulusChecker extends StandardModulusChecker {
    private static Logger LOGGER = LogManager.getLogger(DoubleAlternateModulusChecker.class);

    @Override
    public String getName() {
        return "Double alternate modulus check";
    }

    @Override
    public int getRemainder(String data, int[] weights) {
        return 1;
    }
}

Upvotes: 0

Views: 1172

Answers (2)

Misha Lemko
Misha Lemko

Reputation: 669

Try to use autowiring via constructor instead of field.

public ModulusCheckerMapSupplier (DoubleAlternateModulusChecker doubleAlternateModulusChecker) {
   this.doubleAlternateModulusChecker = doubleAlternateModulusChecker;
}

or using lombok annotation:

@Component
@RequiredArgsConstructor
public class ModulusCheckerMapSupplier implements Supplier<Map> {
   private final DoubleAlternateModulusChecker doubleAlternateModulusChecker;
}

Upvotes: 1

Mạnh Quyết Nguyễn
Mạnh Quyết Nguyễn

Reputation: 18235

You mark @Spy:

@Spy
private DoubleAlternateModulusChecker doubleAlternateModulusChecker;

but not supply an initialization => Null.

If you want to spy on real bean instance, use spring @SpyBean

If you do not have access to that annotation, then use reflection:

@Autowired
private DoubleAlternateModulusChecker doubleAlternateModulusChecker;

@Autowired
private BankDetailsValidator testSubject;  // Important, not @InjectMock

Then in your test:

DoubleAlternateModulusChecker spy = Mockito.spy(doubleAlternateModulusChecker);
ReflectionTestUtils.setField(modulusCheckerMapSupplier, "doubleAlternateModulusChecker", spy);

Upvotes: 1

Related Questions