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
}