How to test a call to external api in Spring Boot - java

I have a method in the service class that uses an external wrapper to call the slack api. The wrapper I'm using is this one if it makes any difference. This is how I'm using the wrapper,
//This is the method in my service class
public String sendMess(SlackObj obj) {
//SlackObj contains the channel url, channel name and the message
//build the payload from the SlackObj
//Slack is the name of the wrapper's class that I'm using
Slack slack = Slack.getInstance();
//slack.send is the method that sends the message to slack
WebhookResponse res = slack.send(url, payload);
//other logic
}
//This is what I've tried
#Test
public void slackSendMessageTest(){
//build the slack obj and payload
//build the mock WebhookResponse
Slack slackMock = mock(Slack.class)
when(slackMock.send(channelUrl, payload)).thenReturn(mockWebHookRes);
assertEquals("SUCCESS", testService.sendMessage(testSlackObj);
}
I am trying to write some tests for this method, so my question is, how would i test it without having the message sent every time I run the test? I believe the cause of this is because slack itself is not mocked and I have no idea as how to inject the mock into the mocked service class.
I am open to refactoring the service class if it helps with the testing. Any suggestions and recommendation is appreciated. Thanks.

You are going to have to find a way to mock Slack, which appears to be a singleton, unfortunately.
Here's what I would do:
1) Make Slack available as a bean that can be autowired:
#Configuration
public class SlackConfiguration {
#Bean
public Slack slack() {
return Slack.getInstance();
}
}
2) Change your class to take an injected Slack:
Note that I am totally guessing on the name here, as you just show the method. You would inject the Slack object you turned into a #Bean above, and not use Slack.getInstance() directly anywhere else.
#Component
public class SlackService {
private final Slack slack;
#Autowired
public SlackService(final Slack slack) {
this.slack = slack;
}
public String sendMessage(final Object message) {
final WebhookResponse res = slack.send(url, payload);
// etc
}
}
3) Mock the Slack object and pass it to your SlackService in test:
This allows you to mock out the implementation of Slack, so you can alter its behavior. I won't go into mocking in detail.
public class SlacServiceTest {
private final Slack slack = mock(Slack.class);
private final SlackService serviceUnderTest = new SlackService(slack);
#Test
public void testSomething() {
// TODO: set mock responses here
// Given... when... then...
}
}

Related

How to parse JWT token in unit tests SpringBoot

I have a microservice setup with Spring boot and OAuth 2 with JWT.
I have additional fields in my JWT token.
Most of my services call a static method that has a thread local of the additional fields in the token.
How can I write unit tests for such services?
Even if I tried to inject a mock user it doesn't work and I couldn't find a way of sending the JWT because I am no testing the controllers.
Code:
SecurityUtils static Calss (also check the package for other relevant JWT handler) .
Example on a method that will call the static class (Line 79).
Method:
public CommonResponse saveUpdateProfile(ProfileRequest request) {
String authUsername = SecurityUtils.getUsername();
Optional<ProfileEntity> optionalProfile = findProfileByUsername(authUsername);
ProfileEntity profile;
if (optionalProfile.isPresent()) {
profile = optionalProfile.get();
} else {
profile = ProfileEntity.builder()
.username(authUsername)
.build();
}
profile.setFirstName(request.getFirstName());
profile.setLastName(request.getLastName());
ProfileEntity savedProfile = profileRepository.save(profile);
if (savedProfile == null) {
throw new RuntimeException("Failed to save user in database");
}
return CommonResponse.ok(savedProfile);
}
I appreciate all the help.
Ok, so that's a common problem when using static methods. You can't easily override them, e.g. in tests. I think what I would do is to turn your SecurityUtils class into a service and make it implement an interface. Then inject this interface into any other service that needs to use it, instead of calling static methods. Then you can easily provide another implementation to your tests.
So you would have something like that:
interface SecurityUtils {
String getUsername();
...
}
#Service
class MySecurityUtils immplements SecurityUtils {
private JwtToken getJwtToken() {
return MySecurityContextHolder.getContext().getJwtToken();
}
public String getUsername() {
return getJwtToken().getUsername();
}
...
}
Then in the unit test you can just inject a mock implementation of SecurityUtils to any class you're testing.

Writing producer test for cloud contract end point using Spring cloud contract

I'm writing a Spring cloud contract into an existing project. When I hit the end point it works fine, but I'm facing issue to set up the messaging side for the producer.
In my controller, I've the following piece of code
{
StudentInfo studentInfo = new StudentInfo();
studentInfo.setProfileId(studId);
studentInfo.setClassDetails(studentService.getClassDetailsInfo(studId));
studentInfo.setMarksInfo(studentService.getMarksInfo(studId));
return employerInfo; }
}
This is an existing end point code in a controller which I cannot change.
Since there are two service calls being called in the method, I'm not able to understand how can I write a producer method which will mock both services response and construct a JSON.
Here is my producer code
StudentInfo mockStudentsResponse = JUnitTestUtil
.toObject(JUnitTestUtil.getFile("studentInfo.json"), StudentInfo .class); // This student info has two class objects inside it 1. ClassDetails and 2.MarksInfo
//How can I mock the response and to which service class
Any ideas would be greatly appreciated.
You should mock out the services inside your controller. When I say service then I mean an application service. Your controller should have application services mocked. That way you send a request to the controller, then the processing of that request ends in the controller. It doesn't go down to any other layers, not to mention get called in other apps.
Example
#RestController
class MyController {
private final Service1 service1;
private final Service2 service2;
#GetMapping("/bla")
String bla() {
String a = service1.a();
String b = service2.b(a);
return a + b;
}
}
Then in the base class you do
class MyBaseClass {
Service1 service1 = Mockito.mock(Service1.class);
Service2 service2 = Mockito.mock(Service2.class);
#BeforeEach
void setup() {
Mockito.when(service1.a()).thenReturn("foo");
Mockito.when(service2.b()).thenReturn("bar");
RestAssuredMockMvc.standaloneSetup(new MyController(service1, service2));
}
}

How to mock feign.Client.Default with Mockito

I'm writing a Dropwizard application and using Feign for building client calls to outside services. I have custom encoders and decoders I'm registering with the feign.Builder like so:
this.feignBuilder = Feign.builder()
.contract(new JAXRSContract()) // we want JAX-RS annotations
.encoder(new JacksonEncoder()) // same as what dropwizard is using
.decoder(new CustomDecoder())
.errorDecoder(new CustomErrorDecoder())
.requestInterceptor(new AuthKeyInterceptor(config.getInterceptor()));
I'm writing unit tests for the feign client calls so I can watch how the feign machinery deals with my encoder/decoder overrides and bubbles on exceptions. I'm not interested in writing integration tests with a fake server right now (this is the most common type of test i see people writing for this situation).
This should be straight forward. I want to mock the point at which feign makes the request and have it return my fake response. That means I should mock the call to feign.Client.Default.execute so it returns my fake response when it makes the request a this call site. An example of what that mock looks like:
String responseMessage = "{\"error\":\"bad\",\"desc\":\"blah\"}";
feign.Response feignResponse = FeignFakeResponseHelper.createFakeResponse(404,"Bad Request",responseMessage);
Client.Default mockFeignClient = mock(Client.Default.class);
try {
when(mockFeignClient.execute(any(feign.Request.class),any(Request.Options.class))).thenReturn(feignResponse);
} catch (IOException e) {
assertThat(true).isFalse(); // fail nicely
}
No luck. The Cleint.Default class isn't mocked when I reach the call site for the request in the code. What am I doing wrong?
As mentioned before, Mockito is not powerful enough.
I solved this with a manual mock.
It's easier than it sounds:
MyService.Java
public class MyService{
//My service stuff
private MyFeignClient myFeignClient;
#Inject //this will work only with constructor injection
public MyService(MyFeignClient myFeignClient){
this.MyFeignClient = myFeignClient
}
public void myMethod(){
myFeignClient.remoteMethod(); // We want to mock this method
}
}
MyFeignClient.Java
#FeignClient("target-service")
public interface MyFeignClient{
#RequestMapping(value = "/test" method = RequestMethod.GET)
public void remotemethod();
}
If you want to test the code above while mocking the feignclient, do this:
MyFeignClientMock.java
#Component
public class MyFeignClientMock implements MyFeignClient {
public void remoteMethod(){
System.out.println("Mocked remoteMethod() succesfuly");
}
}
MyServiceTest.java
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
private MyService myService;
#Inject
private MyFeignClientMock myFeignClientMock;
#Before
public void setUp(){
this.myService = new MyService(myFeignClientMock); //inject the mock
}
//Do tests normally here...
}
It turns out Mockito is not powerful enough to do the thing I assumed it could do. The correct solution is to use PowerMockito to mock the constructor so Client.Default returns the mocked instance when it is instantiated in the class that holds that reference.
After a lot of compilation-error pain I got PowerMockito to compile and it seemed like it was going to work. Alas it failed to return my mock and the calls were still going through. I've tried PowerMockito in the past and never got around to using it because of the extra problems it caused. So I'm still of the opinion that it's not super easy to just plug and play.
It's a shame that trying to do something like this is so hard.

Play Framework 2.2.2 - Java -Testing Controllers With Mock Objects

Is anyone aware of any examples of testing a Java based Play Framework controller by setting mock objects?
I am using Spring in my Play project so all my controller methods are not static.
Testing the tradional way, Play shows my controller as having static methods and I just cant see a way of how I can inject mocks into my object
Result result = callAction(
controllers.routes.ref.LoginController.authenticate(),
fakeRequest().withFormUrlEncodedBody(TestUtils.SUCCESSFUL_LOGIN_MAP)
);
I have a number of services that need to be called in the LoginController and I would like to set those up as mocks
Any help is greatly appreciated
Thanks
Damien
I was looking for the solution of the same problem. So far the best result I was able to achieve is this:
public class MyObjectControllerTest{
private final MyObjectDAO dao = mock(MyObjectDAO.class);
private final MyObjectController controller = new MyObjectController(dao);
public static FakeApplication fakeApplication;
#BeforeClass
public static void startApp() {
fakeApplication = Helpers.fakeApplication();
Helpers.start(fakeApplication);
}
#AfterClass
public static void stopApp() {
Helpers.stop(fakeApplication);
}
#Test(expected = NotFoundException.class)
public void testFailWithUnknownMyObjectKey() throws Throwable {
when(dao.getByKey(any(UUID.class), any(UUID.class), any(Boolean.class))).thenReturn(null);
controller.get(CassandraUUIDs.timeBased());
}
#Test
public void testGetSuccess(){
MyObject deletedObject = MyObjectTestGenerator.generateMyObject();
deletedObject.setDeleted(true);
when(dao.getByKey(any(UUID.class), any(UUID.class), any(Boolean.class))).thenReturn(deletedObject);
try {
Result result = controller.get(CassandraUUIDs.timeBased());
assertThat(status(result)).isEqualTo(Http.Status.GONE);
assertThat(contentType(result)).isEqualTo(Http.MimeTypes.JSON);
assertThat(contentAsString(result)).isEqualTo(ErrorMsg.OBJECT_DELETED.toJson().toString());
} catch (MyObjectsException e) {
e.printStackTrace();
fail("Failed to send MyObject.get request.");
}
}
}
What I do here is instantiate an instance of the controller class and pass mocked DAO instance. Please note that I don't use static controller methods in my code as well.
One issue with this workaround I found so far is that Action (I have custom one) is not working. But Action can (and probably must) be tested separately.

What's th best way to mock a constructor for JUnit?

I have an integrator class which I need to test, the class constructor instantiates a service client. As the service client is an external dependency, I want to test the individual responsibility of the integrator. The code looks like this.
public class Integrator {
Client client;
public Integrator() {
client = new RemoteClient();
}
public String invokeClient() {
....
}
}
What can be the best way to mock this ? Also, I need to make sure of the code coverage.
Use Dependency Injection so you don't create the Client:
public Integrator(Client client) {
//... argument checking omitted
this.client = client;
}
Now your tests can create mock Clients which can be passed in directly.
You can still keep the no-arg constructor as well:
public Integrator() {
this(new RemoteClient());
}

Categories