简体   繁体   中英

In unit test, real code is run instead of function stub & mocked object

It is a very simple unit test case.

I have two methods in class School :

protected S3Object getAwsObject(AmazonS3Client client, String bucketName, String keyName) {
        GetObjectRequest objRequest =  new GetObjectRequest(bucketName, keyName);
        return client.getObject(objRequest);
}

Above method is invoked by the 2nd method showing below:

public void doTask() {
   // get client
   AmazonS3Client client = getAwsS3Client();
   // invoke the 1st method
   S3Object s3Obj = getAwsObject(client, "my-bucket", "my-key");
   ...
}

I use Mockito to test the method doTask() , I tried to mock the AmazonS3Client & stub function getAwsObject() :

@Test
public void testDoTask() {
   // partially mocked School instance
   School school = new School();
   School schoolSpy = Mockito.spy(school);
   // mock the client & s3 object
   AmazonS3Client mockedClient = Mockito.mock(AmazonS3Client.class);

   S3Object mockedS3Obj = Mockito.mock(S3Object.class);

   Mockito.doReturn(mockedClient)
       .when(schoolSpy).getAwsS3Client();

   // PROBLEM HERE: I stub function to return mocked S3Object, but real code is run
   Mockito.doReturn(mockedS3Obj)
          .when(schoolSpy).getAwsObject(mockedClient, "my-bucket", "my-key");

   // system under test
   schoolSpy.doTask();
}

When run the test, I got the following error:

com.amazonaws.services.s3.model.AmazonS3Exception: 
The AWS Access Key Id you provided does not exist in our records. 

(Service: Amazon S3; Status Code: 403; Error Code: InvalidAccessKeyId; Request ID: 6B973FC095C28524),...

Looks like the test case run the real code client.getObject(objRequest) instead of using the stub of getAwsObject(...) , WHY?

If you don't succeed in making your mocking work, you can solve your problem by overriding the original methods this way:

// mock the client & s3 object
final AmazonS3Client mockedClient = Mockito.mock(AmazonS3Client.class);
final S3Object mockedS3Obj = Mockito.mock(S3Object.class);

School school = new School(){

  @Override
  protected S3Object getAwsObject(AmazonS3Client client, String bucketName, String keyName) {
    return mockedS3Obj;
  }

  @Override
  protected AmazonS3Client getAwsS3Client() {
    return mockedClient;
  }

};

// system under test
school.doTask();

First, you should not use spy but in legacy code. ( http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#spy%28T%29 ). If you delegate the creation of objects in not static factories/builders it will be easy to test.

But, in this case, if mockito executes the real code, then your doReturn... is not valid. I think that Mockito does not validate the params clause and then it not launchs the doReturn clause.

You have

doReturn...getAwsObject(mockedClient, "my-bucket", "my-key")

If you do the next and it works then i am right 100%

doReturn...getAwsObject(any(AmazonS3Client.class), anyString(), anyString())

I had problems when i used mock params and not-mock params in the same statement. try this:

getAwsObject(mockedClient, eq("my-bucket"), eq("my-key")); 

If this does not work try with another solution like:

eq(mockedClient), eq(...), eq(...)

Finally, if it does not work, maybe @max solution will be easier.

Multiple wrong things are done here.

  • Spy should not be created for the Class under test.
  • Unit test are written for whole class to test behaviours in the class. If the methods in the same class are Mocked, then whats the purpose of unit tests?
  • Only dependent classes should be mocked and possible provide setter methods to set these dependencies. This way it can be decided whether to inject real objects or mocked objects.

  • The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

     
    粤ICP备18138465号  © 2020-2024 STACKOOM.COM