Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non-void method with doNothing() in Mockito?

I have a unit test to test uploading a file to GCP storage. Here is the code for upload the file.

@Override
public boolean upload(StorageConfiguration storageConfiguration, File file) throws MetaFeedException {
    // get google storage connection.
    Optional<Storage> storage = getStorageConnection(storageConfiguration.getJsonCredentialFilePath());

    // if GCP storage is empty, return empty.
    if (!storage.isPresent()) {
        throw new MetaFeedException("Failed to establish connection with GCP storage");
    }

    // upload blob with given file content
    BlobId blobId = BlobId.of(storageConfiguration.getBucketName(),
            storageConfiguration.getBucketPath().concat(file.getName()));
    BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(storageConfiguration.getContentType()).build();
    try {
        return storage.get().create(blobInfo, Files.readAllBytes(Paths.get(file.getAbsolutePath()))).exists();
    } catch (Exception e) {
        throw new MetaFeedException("Error occurred while uploading file.", e);
    }
}

In my unit tests, I've done something like this,

@Test
public void should_upload_file_to_gcp_with_given_data() throws Exception {
    File tempFile = File.createTempFile("file-name", "1");

    StorageConfiguration storageConfiguration = new StorageConfiguration();
    storageConfiguration.setBucketName("sample-bucket");
    storageConfiguration.setBucketPath("ff/");
    storageConfiguration.setJsonCredentialFilePath("json-credentials");
    storageConfiguration.setContentType("text/plain");

    StorageOptions defaultInstance = mock(StorageOptions.class);
    Storage mockStorage = spy(Storage.class);

    when(defaultInstance.getService()).thenReturn(mockStorage);

    BlobId blobId = BlobId.of(storageConfiguration.getBucketName(), storageConfiguration.getBucketPath().concat(tempFile.getName()));
    BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(storageConfiguration.getContentType()).build();

    doNothing().when(mockStorage).create(blobInfo, Files.readAllBytes(Paths.get(tempFile.getAbsolutePath())));

    boolean upload = gcpStorageManager.upload(storageConfiguration, tempFile);
    Assert.assertTrue(upload);
}

What I'm trying to do is prevent calling the create() method. My point is I don't want to perform a real upload to GCP since it's a test. So I tried as above. But I got an error,

org.mockito.exceptions.base.MockitoException: 
Only void methods can doNothing()!
Example of correct use of doNothing():
    doNothing().
    doThrow(new RuntimeException())
    .when(mock).someVoidMethod();
 Above means:
someVoidMethod() does nothing the 1st time but throws an exception 
the 2nd time is called

UPDATE

Optional<Storage> storage;
    try {
        //connect with the json key file if the key path is not empty
        if (StringUtils.isNotEmpty(jsonCredentialFilePath) && Files.exists(Paths.get(jsonCredentialFilePath))) {
            storage = Optional.ofNullable(StorageOptions.newBuilder()
                    .setCredentials(ServiceAccountCredentials.fromStream(new FileInputStream(jsonCredentialFilePath)))
                    .build().getService());
        } else {
            // if no json key file provided connect to storage without key file.
            storage = Optional.ofNullable(StorageOptions.getDefaultInstance().getService());
        }
    } catch (Exception e) {
        throw new MetaFeedException("Error occurred while connecting to GCP storage", e);
    }

    return storage;

Is there a way to fix this test for uploading file to GCP?

like image 947
codebot Avatar asked Dec 07 '18 09:12

codebot


2 Answers

I had the following problem: I had a method that was returning a Page<...> ,but I can use doNothing() only on void method ,so the fix is easy:

    @Override
    public Page<ThreadServiceModel> findAll(Pageable pageable) {
        return this.findAll(ThreadServiceModel.class, pageable);
    }

And here we go:

        //Will fail and the actualy findAll will be invoked:
        when(this.threadServices.findAll(ThreadServiceModel.class,pageable)).thenReturn(null);

        //Will fail ,cuz you cannot call doNothing() on something that returns values

        doNothing().when(this.threadServices).findAll(ThreadServiceModel.class,pageable);

        //This is the solution ,and when you use doReturn() the actual method will be NOT invoked,
        //+In my case i dont need value , but if i want I cant use...
        doReturn(null).when(this.threadServices).findAll(ThreadServiceModel.class,pageable);

So the solution is simple ,use the last method on the code that I have showed you :)

like image 104
Georgi Peev Avatar answered Oct 17 '22 06:10

Georgi Peev


The actual problem is described in the error message: Only void methods can doNothing()

The create() method is not void, so you cannot mock it with doNoting() so you have to use doReturn instead. In you case you can return a mock of Blob:

Blob mockBlob = mock(Blob.class);
when(mockBlob.exists()).thenReturn(true);
doReturn(mockBlob).when(mockStorage).create(blobInfo, Files.readAllBytes(Paths.get(tempFile.getAbsolutePath())));

This is the first mistake. And the second, it looks like you are not injecting the spied object into you object-under-test. Your method getStorageConnection() will create a new instance of Storage when you call it and does not take into account the spied object. So you are mocking the StorageOptions.class but the StorageOptions.getDefaultInstance() method don't know anything about this mocked object.

So here I can see two paths to fixing your problem. The first one, and it's a more preferable one as for me is to inject dependency either to a method or to your class. Dependency injection to method:

@Override
public boolean upload(Storage storage, File file) throws MetaFeedException {

But this may not be the case, as long as your method overrides some interface, as I assume. So you can move your method getStorageConnection to a separate class StorageProvider and inject the instance of it to you class, and then in the test you can mock a StorageProvider to return a spy of a Storage. In the upload method you will call:

Optional<Storage> storage = storageProvider.getStorageConnection(storageConfiguration.getJsonCredentialFilePath());

The second path, which is rather workaround is to make the method getStorageConnection protected, and the override it in your test to return a spied object. In your test:

gcpStorageManager = new GcpStorageManager() {
  @Override
  protected Storage getStorageConnection(...) {
    return mockedStorage;
  }
}
like image 32
Sergii Bishyr Avatar answered Oct 17 '22 05:10

Sergii Bishyr