Home > other >  How do I mock this client in Java?
How do I mock this client in Java?

Time:10-24

I'm trying to mock this flag client properly in Java but I'm not sure how to do it. Normally, I would mock third party apis via WireMock (mock the call) and it will help me mock it and add a test for it. However, the actual call and logic is masked under this client object, I'm not sure if I'm mocking it properly.

Here's some code from the docs: https://docs.flagsmith.com/clients/server-side#initialise-the-sdk

I have this setup right now in my codebase however:

Implementation:

@Gateway
public class FlagsmithGateway implements FlagsmithPort {

    private final FlagsmithClient flagsmithClient;

    @Autowired
    public FlagsmithGateway(@Value("${flagsmith.environment.id}") String flagsmithEnvironmentId,
                            @Value("${flagsmith.endpoint}") String flagsmithEndpoint) {

        this(FlagsmithClient
            .newBuilder()
            .setApiKey(flagsmithEnvironmentId)
            .withApiUrl(flagsmithEndpoint)
            .build());
    }

    public FlagsmithGateway(FlagsmithClient flagsmithClient) {
        this.flagsmithClient = flagsmithClient;
    }

    @Override
    public boolean isEnabled(FeatureFlags flag) throws FlagsmithClientError {
        Flags flags = flagsmithClient.getEnvironmentFlags();
        return flags.isFeatureEnabled(flag.toString());
    }

    @Override
    public boolean isDisabled(FeatureFlags flag) throws FlagsmithClientError {
        Flags flags = flagsmithClient.getEnvironmentFlags();
        return !flags.isFeatureEnabled(flag.toString());
    }

}

Test class for implementation above:

@ExtendWith(MockitoExtension.class)
public class FlagsmithGatewayTest  {
    
    private FlagsmithGateway flagsmithGateway;

    @Mock
    private FlagsmithClient flagsmithClient;

    @BeforeEach
    public void setup() {
        flagsmithGateway = new FlagsmithGateway(flagsmithClient);
    }

    @Test
    public void isEnabled_shouldReturnWhetherFeatureIsEnabled() throws FlagsmithClientError {
        flagsmithClient = mock(FlagsmithClient.class);
        Flags flags = setupFlags("test_toggle", true);

        when(flagsmithClient.getEnvironmentFlags()).thenReturn(flags);=

        boolean result = flagsmithGateway.isEnabled(FeatureFlags.TOGGLE_FOR_TESTS); 

        assertThat(result).isTrue();
    }

    private static Flags setupFlags(String featureName, Boolean enabled) {
        Flags flag = new Flags();
        BaseFlag baseFlag = new BaseFlag();
        Map<String, BaseFlag> someFlags = new HashMap<>();

        baseFlag.setFeatureName(featureName);
        baseFlag.setEnabled(enabled);
        someFlags.put(featureName,baseFlag);
        flag.setFlags(someFlags);

        return flag;
    }
}

EDIT: The code above almost works but on this line after calling flagsmithGateway.isEnabled(FeatureFlags.TOGGLE_FOR_TESTS) in the tests, I get this an NPE on this line. Flags is null

flags.isFeatureEnabled(flag.toString());

Any reason why? I'm using Junit 5.

CodePudding user response:

To test FlagsmithGateway , you only need to verify if its methods interact correctly with its dependency (i.e. FlagsmithClient) such as if it really calls the expected method with the expected parameters .In this case, you just simply need to mock FlagsmithClient and stub its getEnvironmentFlags().

In order to allow FlagsmithGateway able to use the mocked FlagsmithClient , you need to have some way to pass the FlagsmithClient into it. (e.g via constructor or setter). So firstly , I would refactor your gateway a little bit such that it allows to create with FlagsmithClient through constructor directly.

@Gateway
public class FlagsmithGateway implements FlagsmithPort {

    private final FlagsmithClient flagsmithClient; 

    @Autowired
    public FlagsmithGateway(@Value("${flagsmith.apikey}") String flagsmithApiKey,
                            @Value("${flagsmith.endpoint}") String flagsmithEndpoint) {
        this(FlagsmithClient
            .newBuilder()
            .setApiKey(flagsmithApiKey)
            .withApiUrl(flagsmithEndpoint)
            .build());
    }

    public FlagsmithGateway(FlagsmithClient flagsmithClient) {
        this.flagsmithClient = flagsmithClient;
    }

}

And the test would use this constructor to create FlagsmithGateway :

@ExtendWith(MockitoExtension.class)
public class FlagsmithGatewayTest extends GatewayIntegrationTest {

    FlagsmithGateway flagsmithGateway;

    @Mock
    FlagsmithClient flagsmithClient;

    @BeforeEach
    public void setup() {
        flagsmithGateway = new FlagsmithGateway(flagsmithClient);
    }

    @Test
    public void isEnabled_shouldReturnWhetherFeatureIsEnabled() throws FlagsmithClientError {

        Flags flags = defaultFlagHandler("test_toggle", true);
        when(client.getEnvironmentFlags()).thenReturn(flags);
        
        FeatureFlags featureFlags = xxxxx //create it based on your logic

        boolean result = flagsmithGateway.isEnabled(featureFlags);
        assertThat(result).isTrue();
    }

}

Please note that I am using the plain Mockito test to manually create a FlagsmithGateway but not using spring-boot-test mainly because the way that you setup in spring will make it to use the constructor

new FlagsmithGateway(String flagsmithApiKey,String flagsmithEndpoint) 

to create a FlagsmithGateway but it always hardcode to use the real FlagsmithClient instance.

If you really want to verify the codes in your original constructor works as expected , you can create another test for it :

@Test
public void flagsmithClientCreatedProperly(){
   FlagsmithGateway gateway = new FlagsmithGateway("apiKey" , "http://foo");

  //get the FlagsmithClient from  FlagsmithGateway and the assert tis apiKey and apiUrl 
}
  • Related