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
}
