Fredrik Pedersen
Fredrik Pedersen

Reputation: 51

How do I test a Service with calls to an external API in Spring

Okay, so I am pretty new to testing and Spring Boot in general, so please correct me if I am doing something completely wrong here in the first place.

As a project my team and I are making a Web Application using Spring Boot where we are making calls to the Microsoft Graph API in some of our services. See this service for cancelling an event in a user's calendar:

import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.extensions.IGraphServiceClient;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import org.springframework.stereotype.Service;


@Service
public class CancelEventService {

    public void cancelEvent(final String token, final String id) {

        IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
        IGraphServiceClient graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();

        graphServiceClient.me().events(id)
                .buildRequest()
                .delete();
    }
}

This is working great, but I have been struggling for a couple of days with how to write unit tests for this. The way I see it, I want to either make mocks of the GraphServiceClient, or use a tool like WireMock to make the request go to a mockserver and return some values I configure.

I've tried doing both, but I can't make mocks of GraphServiceClient because it is not a Bean in my project, so I can't figure out how I should proceed to make an implementation I can autowire in to my Service.

When it comes to WireMock I am not even sure I understand if it is capable of doing what I want to, and if it is, I sure haven't been able to configure it correctly (note that we are using JUnit 5 for this project). A simple example where you make a GET-request to Google.com and return "Hello" via WireMock would suffice to get me going here.

Any concrete examples of what I should do, or even just a nod in the right direction would be well appreciated.

Upvotes: 3

Views: 2985

Answers (2)

Fredrik Pedersen
Fredrik Pedersen

Reputation: 51

As a follow up on this, we found a solution to the problem by creating a class GraphServiceClientImpl with a method that returns an instantiated GraphServiceClient.

@Component
public class GraphServiceClientImpl {

    public IGraphServiceClient instantiateGraphServiceClient(final String token) {
        IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
        return GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
    }
}
@Service
public class CancelEventService{

    private GraphServiceClientImpl graphServiceClientImpl;

    public CancelEventService(final GraphServiceClientImpl graphServiceClientImpl) {
        this.graphServiceClientImpl = graphServiceClientImpl;
    }

    public void cancelEvent(final String token, final String id) {

        IGraphServiceClient graphServiceClient = graphServiceClientImpl.instantiateGraphServiceClient(token);

        graphServiceClient
                .me()
                .events(id)
                .buildRequest()
                .delete();
    }
}

Then, our test:

@ExtendWith(MockitoExtension.class)
class CancelEventServiceTest {

    @Mock
    private GraphServiceClientImpl graphServiceClientImpl;

    @InjectMocks
    private CancelEventService cancelEventService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    void cancelEvent_Successful() {

        //given
        IGraphServiceClient serviceClientMock = mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
        given(graphServiceClientImpl.instantiateGraphServiceClient(anyString())).willReturn(serviceClientMock);

        //when
        cancelEventService.cancelBooking("Token", "1");

        //then
        verify(serviceClientMock, times(1)).me();
    }
}

Probably not the optimal solution, but it works. Any other takes on this would be welcome!

Upvotes: 2

develo
develo

Reputation: 76

Well, I cannot assure you that it will work but it will give you a better landscape of the situation:

1) So first, we need to make a slight change on your service. Need to extract IGraphServiceClient from the method so we can mock it later, see:

@Service
public class CancelEventService {

   private IGraphServiceClient graphServiceClient;

   @Autowired
   public CancelEventService(IGraphServiceClient graphServiceClient){
       this.graphServiceClient = graphServiceClient;
   }

    public void cancelEvent(final String token, final String id) {

        IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
        graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();

        graphServiceClient.me().events(id)
                .buildRequest()
                .delete();
    }
}

2) The test would look like this: (Notice that all we are using here is included in spring boot test module, so you shouldn't need to add anything to the project dependencies)

@RunWith(SpringRunner.class)
public class CancelEventServiceTest {

  private IGraphServiceClient graphServiceClientMock;

  private CancelEventService serviceToBeTested;

  @Before
  public void setUp(){

    graphServiceClientMock = Mockito.mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
    serviceToBeTested = new CancelEventService(graphServiceClientMock);
  }

  @Test
  public void test_1() {


    serviceToBeTested.cancelEvent("token", "id");

    verify(graphServiceClientMock, times(1)).me().events("id").buildRequest()
        .delete();
  }
}

Hope it helps!

Upvotes: 4

Related Questions