I have a class which send video as mp4 file to user (Http request/response)
I want to Mock method with main logic to test it. My code
public StreamingOutput videoAsStream(final String videoUrl) {
try {
final URL url = new URL(videoUrl);
return output -> {
try(final InputStream inputStream = url.openConnection().getInputStream()){
IOUtils.copy(inputStream,output);
output.close();
}
};
} catch (final MalformedURLException e) {
log.error("Url exception for url {}",videoUrl);
throw new UncheckedIOException(e);
}
}
What is my way to mock this logic?
The problem is, that URL is final, so you will have to use at least Mockito 2 to mock it. If you are ready to do that, I see two possibilities:
a) Give the url into the method and not the string, thus allowing you to put a mocked url in there. That would be the most simply method. You could also then create a 2nd convenience method that creates said URL from a string. Those two methods will be easier to test because their scope is smaller.
b) Extract the final URL url = new URL(videoUrl); part into a new class, for example a URL Factory, then mock that to return a mocked URL object in your test.
As soon as you produce stuff with "new" inside your method, this method can become harder to test, because you now cannot separate this test from this object generation.
AFAIK, you can't mock final and static methods/classes using mockito. you would have to depend on PowerMockito. I am currently not able to test your method but if you want to mock final/static, you can do as
first, Add the final/static classes to #PrepareForTest then
InputStream mockInputStream = Mockito.mock(InputStream.class);
OutputStream mockOutputStream = Mockito.mock(OutputStream.class);
PowerMockito.mockStatic(IOUtils.class);
Mockito.when(IOUtils.copy(mockInputStream, mockOutputStream)).thenReturn(1L);
Mockito.doNothing().when(mockOutputStream).close();
Let me know if this does not work for you.
Related
I'm writing a Junit to test the following method in Client.java:
public FSDataInputStream getObj(String hName, Path p) throws IOException {
String myKey = pathToKey(hName, p);
FileStatus status = memoryCache.getStatus(p.toString());
if (status == null) {
status = getStatus(hName, p, "getObject");
}
if (status.isDirectory()) {
throw new FileNotFoundException("Can't open " + path
+ " because it is a directory");
}
InputStream inputStream = new InputStream(bucket, myKey,
status.getLen(), client, readAhead, inputPolicy);
return new FSDataInputStream(inputStream);
}
Initially I want to test if status == null then getStatus() is invoked and if status.isDirectory(), the FileNotFoundException is thrown
I'm new to Junit so not completely sure what I'm at but to the best of my knowledge I think I need to mock the following:
List item
Client
status
inputStream
possibly memoryCache
So far this is what I've got:
#Before
public final void before() {
private COSAPIClient myClient;
private String myBucket;
FileStatus myStatus;
InputStream myInputStream;
myClient = PowerMockito.mock(AmazonS3.class);
myInputStream = PowerMockito.mock(InputStream.class);
myFileStatus = PowerMockito.mock(FileStatus.class);
}
#Test
public void getObjTest() throws Exception {
URI uri = new URI("xyz://aa-bb-cc/data7-1-23-a.txt");
String hName = "xyz://aa-bb-cc/";
Path p = new Path("cos://aa-bb-cc/data7-1-23-a.txt");
Configuration conf = new Configuration();
myClient = spy(new Client(uri, conf));
myStatus = spy(new FileStatus());
myMemoryCache.getStatus(p.toString());
InputStream = spy(new InputStream(myBucket, objectKey, 300, myClient, 12345678910L, myInputPolicy));
}
It returns a NullPointerError at this line in my program:
FileStatus status = memoryCache.getStatus(p.toString());
I wonder is anybody could advice if/what I'm doing wronfg and how I should go about resolving this?
First, the real answer: step back for a moment. Don't start with JUnit and Mockito and your production code as input. Rather have a look into a tutorial (like here) that step-by-step explains all the relevant elements and how to "bring" them together.
In your case, the are various problems with your code:
Why are you using PowerMock? Try to go with "plain vanilla" Mockito. If your production code is so that it requires PowerMock, rather consider to rework your production instead of turning to PowerMock.
You seem to really not know where/how to apply mocking. In other words: you only mock the elements that you need to control when running your code under test. And you only use mocking, if you can't control them otherwise. Meaning: you almost never mock a list - you simply create a "normal" list to then add the things that this list should contain.
Creating a mock allows to invoke methods on that mock object. But by default, any method that returns something will return null (or maybe an empty collection, or 0 for primitive return types, see here for details). Thus you rather need a statement such as when(mockedCache.getStatus("some string")).thenReturn(someResult).
I have the following object which I want to test:
public class MyObject {
#Inject
Downloader downloader;
public List<String> readFiles(String[] fileNames) {
List<String> files = new LinkedList<>();
for (String fileName : fileNames) {
try {
files.add(downloader.download(fileName));
} catch (IOException e) {
files.add("NA");
}
}
return files;
}
}
This is my test:
#UseModules(mockTest.MyTestModule.class)
#RunWith(JukitoRunner.class)
public class mockTest {
#Inject Downloader downloader;
#Inject MyObject myObject;
private final String[] FILE_NAMES = new String[] {"fail", "fail", "testFile"};
private final List<String> EXPECTED_FILES = Arrays.asList("NA", "NA", "mockContent");
#Test
public void testException() throws IOException {
when(downloader.download(anyString()))
.thenThrow(new IOException());
when(downloader.download("testFile"))
.thenReturn("mockContent");
assertThat(myObject.readFiles(FILE_NAMES))
.isEqualTo(EXPECTED_FILES);
}
public static final class MyTestModule extends TestModule {
#Override
protected void configureTest() {
bindMock(Downloader.class).in(TestSingleton.class);
}
}
}
I am overwriting the anyString() matcher for a specific argument. I am stubbing the download() method so that it returns a value for a specific argument and otherwise throws an IOException which gets handled by MyObject.readFiles.
The weird thing here is that the second stub (downloader.download("testFile")) throws the IOException set in the first stub (downloader.download(anyString())). I have validated that by throwing a different exception in my first stub.
Can someone explain me why the exception is thrown when adding an additional stub? I thought that creating a stub does not call the method/other stubs.
The problem is that when you write
when(downloader.download("testFile")).thenReturn("mockContent");
the first thing to be called is downloader.download, which you've already stubbed to throw an exception.
The solution is to use the slightly more versatile stubbing syntax that Mockito provides. This syntax has the advantage that it doesn't call the actual method when stubbing.
doThrow(IOException.class).when(downloader).download(anyString());
doReturn("mock content").when(downloader).download("test file");
I have listed other advantages of this second syntax, in my answer here
I thought that creating a stub does not call the method/other stubs.
This assumption is wrong, because stubbing is calling the mocks methods. Your test methods are still plain java!
Since stubbing for anyString will overwrite stubbing for any specific string you will either have to write two tests or stub for two specific arguments:
when(downloader.download("fail")).thenThrow(new IOException());
when(downloader.download("testFile")).thenReturn("mockContent");
Mockito is a very sophisticated piece of code that tries its best so that you can write
when(downloader.download(anyString())).thenThrow(new IOException());
which means “when the downloaders mock download method is called with anyString argument thenThrow an IOException” (i.e. it can be read from left to right).
However, since the code is still plain java, the call sequence actually is:
String s1 = anyString(); // 1
String s2 = downloader.download(s1); // 2
when(s2).thenThrow(new IOException()); // 3
Behind the scenes, Mockito needs to do this:
register an ArgumentMatcher for any String argument
register a method call download on the downloader mock where the argument is defined by the previously registered ArgumentMatcher
register an action for the previously registered method call on a mock
If you now call
... downloader.download("testFile") ...
the downloader mock checks whether there is an action register for "testFile" (there is, since there is already an action for any String) and accordingly throws the IOException.
Your 2nd mock statement is getting overriden by the first mock statement (because both mock statements are passing a String argument). If you want to cover try as well as catch back through your mock test then write 2 different test cases.
Java Path API is a better replacement of Java File API but massive usage of static methods makes it difficult to mock with Mockito.
From my own class, I inject a FileSystem instance which I replace with a mock during unit tests.
However, I need to mock a lot of methods (and also creates a lot of mocks) to achieve this. And this happens repeatedly so many times across my test classes. So I start thinking about setup a simple API to register Path-s and declare associated behaviour.
For example, I need to check error handling on stream opening.
The main class:
class MyClass {
private FileSystem fileSystem;
public MyClass(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
}
The test class:
class MyClassTest {
#Test
public void operation_encounterIOException() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
doThrow(new IOException("fileOperation_checkError")).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
#Test
public void operation_normalBehaviour() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
ByteArrayInputStream in = new ByteArrayInputStream(/* arranged content */);
doReturn(in).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
}
I have many classes/tests of this kind and mock setup can be more tricky as static methods may call 3-6 non-static methods over the Path API. I have refactored test to avoid most redundant code but my simple API tends to be very limited as my Path API usage grown. So again it's time to refactor.
However, the logic I'm thinking about seems ugly and requires much code for a basic usage. The way I would like to ease API mocking (whatever is Java Path API or not) is based on the following principles:
Creates abstract classes that implements interface or extends class to mock.
Implements methods that I don't want to mock.
When invoking a "partial mock" I want to execute (in preference order) : explicitly mocked methods, implemented methods, default answer.
In order to achieve the third step, I think about creating an Answer which lookup for implemented method and fallback to a default answer. Then an instance of this Answer is passed at mock creation.
Are there existing ways to achieve this directly from Mockito or other ways to handle the problem ?
Your problem is that you are violating the Single Responsibility Principle.
You have two concerns:
Find and locate a file, get an InputStream
Process the file.
Actually, this should most likely be broken into sub concerns also, but that's outside the scope of this question.
You are attempting to do both of those jobs in one method, which is forcing you to do a ton of extra work. Instead, break the work into two different classes. For example, if your code were instead constructed like this:
class MyClass {
private FileSystem fileSystem;
private final StreamProcessor processor;
public MyClass(FileSystem fileSystem, StreamProcessor processor) {
this.fileSystem = fileSystem;
this.processor = processor;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
processor.process(in);
} catch (IOException e) {
/* business error management */
}
}
}
class StreamProcessor {
public StreamProcessor() {
// maybe set dependencies, depending on the need of your app
}
public void process(InputStream in) throws IOException {
/* file content handling */
}
}
Now we've broken the responsibilities into two places. The class that does all the business logic work that you want to test, from an InputStream, just needs an input stream. In fact, I wouldn't even mock that, because it's just data. You can load the InputStream any way you want, for example using a ByteArrayInputStream as you mention in your question. There doesn't need to be any code for Java Path API in your StreamProcessor test.
Additionally, if you are accessing files in a common way, you only need to have one test to make sure that behavior works. You can also make StreamProcessor be an interface, and then, in the different parts of your code base, do the different jobs for different types of files, while passing in different StreamProcessors into the file API.
In the comments you said:
Sounds good but I have to live with tons of legacy code. I'm starting to introduce unit test and don't want to refactor too much "application" code.
The best way to do it is what I said above. However, if you want to do the smallest amount of changes to add tests, here is what you should do:
Old code:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
New code:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
new StreamProcessor().process(in);
} catch (IOException e) {
/* business error management */
}
}
public class StreamProcessor {
public void process(InputStream in) throws IOException {
/* file content handling */
/* just cut-paste the other code */
}
}
This is the least invasive way to do what I describe above. The original way I describe is better, but obviously it's a more involved refactor. This way should involve almost no other code changes, but will allow you to write your tests.
I have a class with a few methods advised through an input validation aspect (validates whether all input parameters are not-null/non-empty strings).
I am facing an issue while writing test case for them and want to verify if this is indeed a bad design issue.
Here's a very simplified version of my class:
public class A {
public String one(String word) {
// Some actions
String val = two(word2);
// Some more actions
}
protected String two(String word) {
// Some actions
}
}
Now while writing test cases for one() I use Mockito and want to mock calls to two(). So I use:
#Spy
A a;
#Test
void test() {
doReturn("Bye").when(A).two(Mockito.anyString());
a.one("hello");
// Some validations
}
This test fails as the: doReturn() line fails with input being empty for two().
Should I not mock two() or can I make this work somehow?
Edit:
Adding a more specific example related to the two methods being present in two different classes as requested:
Create a page through a WebService. This builds a putRequest, executes it and returns a response.
public class AUtility implements BaseUtility {
public Response create(Params params) {
try {
PutMethod putRequest = buildPUTRequest(params.getAttr1(), params.getAttr2());
return Utils.buildResponse(client.executeMethod(putRequest),
params.getAttr3(),
params.getAttr4());
} catch (Exception e) {
throw new AppException(e);
}
}
}
The put request marshals the data into a file to write it through the HttpClient
private PutMethod buildPUTRequest(final String url, final Object obj) throws IOException, JAXBException {
// Create a temp file to store the stream
File tempFile = File.createTempFile(APPLICATION_LABEL, XML_LABEL);
decoder.marshal(obj, tempFile);
// Build the put method
return putMethod;
}
XMLMarshaller
public interface XMLDecoder implement Decoder {
public void marshal(Object obj, File tempFile) throws IOException, JAXBException {
// Perform marshalling operations
}
}
The test fails on line2 with the inputs being null.
#Test
public void createPageParamsHttpException() throws HttpException, IOException, JAXBException {
expectedException.expect(AppException.class);
doNothing().when(decoder).marshal(Mockito.anyString(), Mockito.any(File.class));
doThrow(HttpException.class).when(client).executeMethod(Mockito.any(HttpMethod.class));
Params params = new Params(new Application(),
APPLICATION_URL_LABEL,
SITE_NAME_LABEL,
URL_WITHOUT_HTTP_N_HTML);
utility.createPage(params);
}
Any idea how should I proceed for the same?
You don't want to do this.
You are inherently changing the behavior of the class. If you change what two() does, how do you know that one() will do what it's supposed to do in production?
If you truly want to do this, you should extract the behavior of two() into another top level class, and then inject the dependency into A. Then you can mock this dependency and you don't have to worry about going to the trouble of creating a partial mock for A.
In a similar vein, if you must keep two in the same class (because it's behavior is part of the same responsibility that is assigned to A - see the Single Responsibility Principle - why is it public?
The reason you are having trouble is because you are violating the SRP, see my note above. You said this:
This builds a putRequest, executes it and returns a response.
You should not be trying to test the behavior of all three of those things at the same time. Ultimately, this method does not really do anything. The buildPUTRequest method does, and shouldn't be in a class called AUtility, it should be in a class RequestFactory. Then, you would want to test the Utils.buildResponse method, except that shouldn't be in a class called Utils, it should be in a class called Responder or something... and this method ABSOLUTELY should not be static.
Work on naming your classes better things, and if you can't come up with a good name, that means the class probably does too much and should be refactored. And a method that wraps the work in two other methods doesn't need to be unit tested. Integration tested, perhaps, but that's another story.
I'm trying to write JUnit tests for my code but with in some of my methods other methods are called. Is it possible to mock these calls out?
E.g.
s3FileWrite(File file, Status status)
{
S3 s3 = new S3(file.getName, s3Service)
String key = s3.getKey();
String bucket = s3.getBucket();
File tmp = new File("tmp/" + s3.getName());
writeFile(key, bucket, tmp, status); //local method call I want to mock out
}//awsFileWrite
The writeFile method is what I want to mock out and it's part of the class I am testing, but I don't know how to mock it out. I thought mocking out the class I'm testing and then adding the call to my expectations would do it but it still calls the method.
Can anyone give me some advice on what to do here please?
EDIT:
My JMock code looks like this:
#Test
public void testS3FileWrite()
{
fileName = context.mock(File.class);
s3Service = context.mock(FileDataAccessor.class);
s3 = context.mock(S3.class);
reportWriter = context.mock(ReportWriter.class);
try
{
context.checking(new Expectations(){{
oneOf(fileMetaData).getKey();
will(returnValue("s3Key"));
oneOf(fileMetaData).getBucketName();
will(returnValue("BucketName"));
oneOf(fileMetaData).getName();
will(returnValue("TempFile"));
((MethodClause) oneOf (any(File.class))).method("File").with(same("tmp/TempFile"));
oneOf(reportWriter).writeFile(with(same("s3Key")),
with(same("BucketName")),
with(any(File.class),
with(same(Status.OK)));
}});//Expectations
}
catch (Exception e)
{
ErrorStatus.debug("Exception in ReportTest.testS3FileWrite: " + e);
}//try-catch
ReportWriter test = new ReportWriter(status);
test.awsFileWrite(fileName, Status.OK);
}//testAWSFileWrite
PowerMock lets you partially mock classes, but it's designed for EasyMock not JMock. In any case, this is not the best approach.
Add a new class FileWriter and move the writeFile method to it, then in your class under test,
delegate to one:
// default implementation, can be replaced in tests
FileWriter fileWriter = new FileWriter();
writeFile(key, bucket, tmp, status) {
fileWriter.write(key, bucket, tmp, status);
};
In your test code, overwrite the fileWriter field in a the class under test (add a setter or make the field protected) with a mock FileWriter.