0

I encountered a problem while trying to test Class, which uses external API -

Jama Software

. Let's say there is a ClassA. It has 2 methods,that I want to test using JUnit. Both methods have ClassB as an argument.

ClassB is belonging to another API. I can see it's methods but the implementation details are not available.(compiled code). The question is: How can I mock this classes to make TestClass isolated? I know that in Mockito there is an option to hardcode responses. Is there any better/cleaner way of doing this, so I don't need to manually configure mock objects? I would be grateful for any advices on that topic.

Dago
  • 628
  • 1
  • 9
  • 22
  • The cleanest solution is to instantiate/obtain valid objects of the third-party classes, and use them in your tests. Do not mock them unless you run into some technical difficulty which cannot be satisfactorily solved without mocking. Trust me (10 years experience with it), mocking is best left for exceptional cases. – Rogério Jun 21 '16 at 16:51
  • so basically we can't isolate our test in such scenario? Just use 3 party software like we normally do in the project? – Dago Jun 21 '16 at 21:29
  • unless you are integration testing I wouldn't use a real object if class B object - especially if you don't have the sources. You don't want to test classB - you want to test your classA. Hardcoding responses is what you want to have a defined test configuration to test YOUR class. If its hard to configure the mock - I would look at the method if its doing too much or has law of demeter violations. – Rhayene Jun 21 '16 at 21:35
  • so i have 3 pardy class and its interface, i dont know its implementation. To isolate test i can mock this class and need to configure it by hand to set what things each method should return. I can't avoid that piece of work if I want to achieve isolation. I am confused beacuse there are ways to mimic database instead of operating on real one and thats way i am trying to be sure if there is any easy way to CONFIGURE that. – Dago Jun 21 '16 at 21:56
  • Can you provide some information, what the offending class does? Choosing when to mock, when to use the real object or when to write a fake is heavily dependent on the task at hand. Especially data stores may be condidates for faking instead of mocking, whereas mocking yields advantages for simulating exceptional behaviour. – Max Fichtelmann Jun 21 '16 at 22:02
  • sure, i will provide this information this morining. I can't do it right now. Thank you for ur time. – Dago Jun 21 '16 at 22:09
  • API's class methods returns long,string and void. Unfortunately some methods use fields that can be set and use it to calculate it the return values. That means i need to know what exactly each methods will return in particular cases, and i can't configure this object easily.? – Dago Jun 22 '16 at 08:49
  • @Dago could you provide a code example that summarizes/simulates those api calls (needed setters on fields included)? Its hard to give you more than a generic answer if we don't know what you are doing and where configuring the mock is not easy/complicated. – Rhayene Jun 22 '16 at 15:45

1 Answers1

1

A good read about mocking from another answer.

The point of using the mock with its methods is to isolate it from its dependencies. Mocking arguments may seem dirty or un-fancy, but it's designed to allow you to test the expectations of the mocked class. In this case, you assume that whatever your service ClassB is, works as intended (or not, if you expect exceptions as well). In this case, we are testing a single unit ClassA and assume that ClassB is correct.

I'll go ahead and make a more concrete example, similar to the waiter/chef example above.

Let's say your ClassA is a CookingPot and your ClassB is a Pantry. Any time you interact with the Pantry to getPasta, it tries to send you Pasta. If it has no Pasta, it returns null.

Pantry is a fancy Pantry. It has a table storing its contents, so you don't know if it has or doesn't have Pasta. This is "complex" in the real world, but this isn't our Pantry. We have no idea how it works, just that it either returns Pasta or it doesn't. We don't know the logic, nor do we have to. We know what it does, so if we want to make sure we can add pasta to our pot, we can use a mock Pantry that acts the same way, without having the fancy bells whistles the real one does.

Pantry also has a bunch of other methods, such as allowing you to add your leftover Pasta back to the shelf. You have no idea how the smart Pantry puts the pasta back in, but you don't need to model that, because Mockito is fine with void calls regardless.

If ClassB were to change, these tests might still pass, but you'd catch the issue in integration tests and say "hmm, my class still works, but something is up with the new Pantry so I need to redo the class, and rewire the tests."

CookingPot {
    void addPasta(Pantry pantry) {
        Pasta p = pantry.getPasta();
        System.out.println("Added " + p.toString() + " to the pot!"); 
        //fancy actions that use up some of the Pasta
        pantry.add(p); //return the pasta
    }
}

Pantry {
    Pasta getPasta(); //we have no idea what this does in actuality
}


@Test(expected = NullPointerException.class)
public void testAddPastaButNoPastaLeft() {
    Pantry mockPantry = mock(Pantry.class);

    CookingPot cookingPot = new CookingPot();
    cookingPot.addPasta(mockPantry);
    //we should probably have checked if there was pasta before 
    //attempting to add imaginary pasta
}

@Test
public void testAddPasta() {
    Pantry mockPantry = mock(Pantry.class);
    when(mockPantry.getBaz()).thenReturn(new Pasta());

    CookingPot cookingPot = new CookingPot();
    cookingPot.addPasta(mockPantry);
}
Community
  • 1
  • 1
Compass
  • 5,502
  • 4
  • 27
  • 40
  • thanks for respond. Unfortunately i dont understand two things. You gave ClassA and ClassB new names, but in testAddPasta() i see mocmClassB.getBaz() and i dont know what is this in thac case :) – Dago Jun 21 '16 at 21:35
  • 1
    @Dago typo. I was originally going to use the basic class names, but that was messy to read. Basically, you can just mock the API calls. However, typically, you'd want to make your own Pantry object as a service that uses JamaPantry instead. That way, if JamaPantry changes, you just change your Pantry to handle JamaPantry, rather than change everything else to handle JamaPantry. – Compass Jun 21 '16 at 22:07
  • ok i try to make this clear :) I want to know if there is any posibility to isolate my test class from other dependencies, miminic API's class calls without setting expectation by myself but symulate API behaviour and connect to them like,, virtually,,? If i would use services that uses ClassB and used it to test, that would mean i dont isolate test but inject dependency by that specyfic service? – Dago Jun 22 '16 at 08:57