0

I am having thoughts on how do I write a test case for this using mockito.

Example, part of my logic in my main thread is to create a thread that does 3 things. Please see my annotated code below.

Now the RequestThread can be spawn many times depending on the number of inputs coming from the main program.

public class MainThreads {
    public static void main(String[] args) {
        RequestThread rt = new RequestThread("sample");
        rt.start();

        //RequestThread another = new RequestThread("sample-2");
        //another.start();

        //RequestThread newThread = new RequestThread("sample-3");
        //newThread.start();
    }

    public static class RequestThread implements Runnable{
        private final String request;

        public RequestThread(String request) {
            this.request = request;
        }

        @Override
        public void run() {
            //1. Instantiate a service passing the required request parameter
            MyDataWebService service = new MyDataWebService(request);

            //2. Get the returned data
            List<String> dataList = service.requestData();

            //3. Write to file
            Path file = Paths.get("/someDir/" + request);
            Files.write(file, dataList, Charset.forName("UTF-8"));
        }

    }
}

My issue is this, I could not figure out how to properly write a JUnit/Mockito test for a threaded class. I am not that well verse on Mockito and JUnit in general so I am looking for a way to unit test a threaded application.

Can somebody guide me on how can I unit test such thing?

Himanshu Bhardwaj
  • 4,038
  • 3
  • 17
  • 36
Mark Estrada
  • 9,013
  • 37
  • 119
  • 186
  • Can you mock out the threads? Also maybe consider using a thread-pool? – Rick Jul 25 '18 at 05:44
  • That is actually my question... I am not sure how to mock out my request. I haven't use thread pool so let us just say I will just spawn one thread – Mark Estrada Jul 25 '18 at 06:10
  • You do realise, your code is not multi-threaded right now, you should be using start() method not run() method. – Himanshu Bhardwaj Jul 25 '18 at 06:21
  • @HimanshuBhardwaj Ohh Sorry.. I just type everything in... There was a lot of code going on...so I strip the non-relevant part...Thank you for pointing this out – Mark Estrada Jul 25 '18 at 06:23
  • Is it possible to do a unit test of such thread? So that thread does a lot of things...I cannot find a way on Mockito on how to unit test or mock such thing. Any hints? – Mark Estrada Jul 25 '18 at 06:25
  • Well for first the method returns nothing its a void. If you have thought of writing a test. What kind of expectations do you think should be there? Maybe then we can help translating. – Himanshu Bhardwaj Jul 25 '18 at 06:30
  • What is the thing you would like to mock? Is it the `MyDataWebService` object that is inside `RequestThread`? Or is it the whole `RequestThread`? In any case you would have to rewrite some bits of your code, in general don't call `new [MyClass]` in any function that you want to test if your goal is to mock `MyClass`. – Joel Jul 25 '18 at 07:09
  • @Joel Yes, I mean the whole RequestThread..if its possible to do a unit test of such – Mark Estrada Jul 25 '18 at 07:12

2 Answers2

2

You need to bring some changes to your code in order to make it more testing-friendly. In particular:

  • Objects that you want to mock should implement an interface
  • Do not instantiate objects to mock in the function that you want to test

Here is a rewrite of the classes so that you can mock MyDataWebService and test RequestThread. Based on this example you will more easily be able to write a full test for the MainThreads class.

public class MainThreads {
    public static void main(String[] args) {
        RequestThread rt = new RequestThread("sample");
        rt.start();

        //RequestThread another = new RequestThread("sample-2");
        //another.start();

        //RequestThread newThread = new RequestThread("sample-3");
        //newThread.start();
    }

    public static class RequestThread extends Thread {
        private final String request;
        // One important thing to note here, "service" has to be non-final. Else mockito won't be able to inject the mock.
        private MyDataWebServiceInterface service;

        public RequestThread(String request) {
            this.request = request;
            //1. Instantiate a service passing the required request parameter
            // => do it in constructor, or passed as parameter, but NOT in the function to test
            service = new MyDataWebService(request);
        }

        @Override
        public void run() {
            //2. Get the returned data
            List<String> dataList = service.requestData();

            //3. Write to file
            Path file = Paths.get("someDir/" + request);
            try {
                Files.write(file, dataList, Charset.forName("UTF-8"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

The interface & implementation for MyDataWebService:

interface MyDataWebServiceInterface {
    List<String> requestData();
}

class MyDataWebService implements MyDataWebServiceInterface {
    public MyDataWebService(String request) {
    }

    @Override
    public List<String> requestData() {
        return Arrays.asList("foo", "bar");
    }
}

And a test using mockito. Note, the checks for existing file and thread sleeping may not be the most elegant thing to do here. If you can afford adding some marker in RequestThread to indicate that the data has been written, it would certainly make the test better and safer (filesystems i/o are sometimes tricky to test).

@RunWith(MockitoJUnitRunner.class)
public class RequestThreadTest {

    private static final Path FILE = Paths.get("someDir", "sample");

    @Mock
    MyDataWebServiceInterface service;

    @InjectMocks
    MainThreads.RequestThread reqThread = new MainThreads.RequestThread("sample");

    @Before
    public void setup() throws IOException, InterruptedException {
        if (Files.exists(FILE)) {
            Files.delete(FILE);
            while (Files.exists(FILE)) {
                Thread.sleep(50);
            }
        }
    }

    @Test
    public void shouldWriteFile() throws InterruptedException {
        Mockito.when(service.requestData()).thenReturn(Arrays.asList("one", "two"));
        reqThread.start();
        while (!Files.exists(FILE)) {
            Thread.sleep(50);
        }
        // HERE run assertions about file content
    }
}

Now, testing asynchronous code is often more complicated than synchronous because you will often face non-determinist behaviours, timing issues, etc. You may want to set a timeout on your test, but remember: continuous integration tools (jenkins, travis etc.) will often run slower than your machine, it's a common cause of problems, so don't set it too tight. As far as I know there is no "one-fits-all" solution for non-determinist issues.

There's an excellent article about non-determinism in tests by Martin Fowler: https://martinfowler.com/articles/nonDeterminism.html

Joel
  • 2,374
  • 17
  • 26
  • 1
    I wish I could save this reply in order to refer back to it later. Or else, failing that, tattoo it (reversed so they can read it in a mirror) on certain people's foreheads ... :D – Rick Jul 26 '18 at 01:15
1

A distinctive non-answer: in 2018, you don't use "raw" threads any more.

Java has much better abstractions to offer by now, for example the ExecutorService. And guess what: when you have your code submit tasks into such a service, you can probably test it using a same-thread executor service.

Meaning: by using such abstractions and dissecting your delivery into specific services, you might be able to (almost) fully test not only the small units, but also how tasks come into your system and worked on.

In other words: you unit test your "tasks", then you "unit" test the integration of tasks when they go into such an executor. Then you are only left with a bit of real function/integration testing to check that the "true parallel" solution behaves as expected.

Anything else gets complicated quickly. Using real threads in ordinary unit tests can lead to inconsistent behavior, or increased runtimes (like the test waiting for threads to asynchronously doing something).

As in your example: your test would simply sit there and regularly check if the expected file was written with the expected content. Leading to: how long should it wait before failing? Waiting not long enough means that your test will occasionally fail because code sometimes just takes longer. If you wait too long, that adds up to the overall time you need to run your tests. You don't want to end up with hundreds of unit tests were some need 10, 20 seconds because "waiting for other threads".

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • I haven't use executor service honestly but I will take a look at refactoring my code. But for my unit test learning, is it not possible to unit test such case? I mean my current implementation. Thank you. – Mark Estrada Jul 25 '18 at 06:56