1

I am trying to write a unit test for a helper method in my ApiRequestHandler which handles APIGatewayProxyRequestEvent . This class uses an EFS file path, which is where it writes files to. I have this stored in my codebase in public final class Constants as public static final String EfsMountPath.

Obviously, I cannot write to folder Constants.EfsMountPath on my Windows machine...

Problem is, I cannot test-double out its value, without causing issues. Attempt to use MockFree([Constants]) breaks my test case space.

The method I am trying to test, is defined to be:

RequestBodyModel parseRequestEvent(APIGatewayProxyRequestEvent request) {
    byte[] bodyBytes = request.getBody().getBytes("UTF-8");
    if (request.getIsBase64Encoded())
        bodyBytes = Base64.getDecoder().decode(bodyBytes);

    RequestBodyModel model = new RequestBodyModel();

    FileUpload.parse(bodyBytes, request.getHeaders().get('Content-Type'))
        .each { FileItem fileItem ->
            if (fileItem.getFieldName().equals("inputFile")) {
                File inputFile = FileUtils.CreateFileIfNotExists("${Constants.EfsMountPath}/${FileUtils.InputPathPart}/${fileItem.getName()}");
                fileItem.write(inputFile);
                model.inputFile = inputFile;
                return;
            }

            if (fileItem.getFieldName().equals("outputFileExtension")) {
                model.outputFileExtension = fileItem.getString();
                return;
            }

            if (fileItem.getFieldName().equals("shouldExportDncRecords")) {
                model.shouldExportDncRecords = Boolean.valueOf(fileItem.getString());
                return;
            }
        }

    return model;
}

What is the best way to make this testable w.r.t. the input file path ?

2 Answers 2

1

One way is to exploit that Groovy allows parameters with default values.

RequestBodyModel parseRequestEvent(APIGatewayProxyRequestEvent request, Constants constants = new Constants()) {    
    byte[] bodyBytes = request.getBody().getBytes("UTF-8");
    if (request.getIsBase64Encoded())
        bodyBytes = Base64.getDecoder().decode(bodyBytes);

    RequestBodyModel model = new RequestBodyModel();

    FileUpload.parse(bodyBytes, request.getHeaders().get('Content-Type'))
        .each { FileItem fileItem ->
            if (fileItem.getFieldName().equals("inputFile")) {
                File inputFile = FileUtils.CreateFileIfNotExists("${constants.EfsMountPath}/${FileUtils.InputPathPart}/${fileItem.getName()}");
                fileItem.write(inputFile);
                model.inputFile = inputFile;
                return;
            }

            if (fileItem.getFieldName().equals("outputFileExtension")) {
                model.outputFileExtension = fileItem.getString();
                return;
            }

            if (fileItem.getFieldName().equals("shouldExportDncRecords")) {
                model.shouldExportDncRecords = Boolean.valueOf(fileItem.getString());
                return;
            }
        }

    return model;
}

Done this way constants is an overridable default value that your test suite is free to change to whatever it wants.

Passing it in at the method level may seem a little klunky. And it is. This could also have been passed to the constructor that built the object that houses this method.

You should provide a way to configure your code base differently for multiple builds. Not focus only on one and make others hack their way in.

1

I ended up doing the following:

  • create a PathProvider that has two methods:
    • getBaseInputPath
    • getBaseOutputPath

It is defined to be:

class PathProvider {
    String getBaseInputPath() {
        return "${Constants.EfsMountPath}/${FileUtils.InputPathPart}";
    }

    String getBaseOutputPath() {
        return "${Constants.EfsMountPath}/${FileUtils.OutputPathPart}";
    }
}
  • put a child PathProvider in my ApiRequestHandler class
  • in the test case, we create instance of ApiRequestHandler with a TestPathProvider, defined to be:
class TestPathProvider extends PathProvider {
    @Override
    String getBaseInputPath() {
        return "${FileUtils.TestDirectoryPath}/input";
    }

    @Override
    String getBaseOutputPath() {
        return "${FileUtils.TestDirectoryPath}/output";
    }
}

The parseRequestEvent then becomes:

    RequestBodyModel parseRequestEvent(APIGatewayProxyRequestEvent request) {
        byte[] bodyBytes = request.getBody().getBytes("UTF-8");
        if (request.getIsBase64Encoded())
            bodyBytes = Base64.getDecoder().decode(bodyBytes);

        RequestBodyModel model = new RequestBodyModel();

        FileUpload.parse(bodyBytes, request.getHeaders().get('Content-Type'))
            .each { FileItem fileItem ->
                if (fileItem.getFieldName().equals("inputFile")) {
                    File inputFile = FileUtils.CreateFileIfNotExists("${this.pathProvider.getBaseInputPath()}/${fileItem.getName()}");
                    fileItem.write(inputFile);
                    model.inputFile = inputFile;
                    return;
                }

                if (fileItem.getFieldName().equals("outputFileExtension")) {
                    model.outputFileExtension = fileItem.getString();
                    return;
                }

                if (fileItem.getFieldName().equals("shouldExportDncRecords")) {
                    model.shouldExportDncRecords = Boolean.valueOf(fileItem.getString());
                    return;
                }
            }

        return model;
    }

This takes care of other test cases too, such as testing the actual handleRequest method, that is to create an output file and return it in an ApiGatewayProxyResponseEvent...but I have yet to write those

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.