Browse DevX
Sign up for e-mail newsletters from DevX


Unit Test More Efficiently with Mock Object Alternatives : Page 3

The mock-object testing pattern has commonly been used to test an individual unit of code without testing its dependencies. While this pattern works well for interaction-based testing, it can be overkill for state-based testing. Learn how to streamline your unit-testing using stubs and the pseudo-objects testing pattern.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Pseudo Objects
Here's a scenario where mock objects may be a bit of overkill. Suppose you're going to test a method, and the path you're going to test shouldn't reference a particular object that is passed into the method as an argument. For example, say you want to test the boundary conditions in the following method:

public static Report createMonthlyReport(int month, int year, Account account) { if (month < 1 || month > 12) { throw new IllegalArgumentException(); } if (year < 1975 || year > getCurrentYear()) { throw new IllegalArgumentException(); } . . . }

Your test needs to pass in values that will cause the IllegalArgumentException to be thrown. This path through the createMonthlyReport() method will not access the Account object that is passed in; in fact, you want to ensure that the method doesn't reference the Account object. You could pass in a mock object, but you would have to do a bit of setup in your test, as shown below:

public void testMonthCannotBeNegative() { AccountMock account = new AccountMock(); account.setExpectedGetBalanceCalls(0); account.setExpectedAddEntryCalls(0); account.setExpectedGetEntriesForCalls(0); // .. etc int month = -1; int year = 1999; try { ReportFactory.createMonthlyReport(month, year, account); fail("Exception should have been thrown on negative month"); } catch (IllegalArgumentException e) { ; } }

This test ensures that none of the methods on the Account object is called, and it does this by setting the "expected calls" values for each method on the mock to 0.

Now, take a look at a simpler technique that accomplishes the same thing. Instead of creating a mock object that implements the Account interface, create what I call a "pseudo object." A pseudo object does nothing more than extend an interface and throw exceptions on any method call. In the case of Account, the AccountPseudo would look like this:

public class AccountPseudo implements Account { public void addEntry(Date date, double amount) { throw new PseudoError(); } public List getEntriesFor(int month, int year) { throw new PseudoError(); } public double getBalance() { throw new PseudoError(); } } public class PseudoError extends RuntimeException { }

As you can see, this is definitely not a mock object. There are no methods defined above and beyond what is available on the interface. The pseudo doesn't remember any object state or verify anything during or after test execution; it simply throws an exception on any method call. Now, this is not necessarily a stub, because it is not providing any canned values that a test may use. But, as you'll see shortly, it is a very convenient class to extend when you need a stub.

If you wanted to use a pseudo instead of the mock, your test code would look something like this:

public void testMonthCannotBeNegative () { Account account = new AccountPseudo(); int month = -1; int year = 1999; try { ReportFactory.createMonthlyReport(month, year, account); fail("Exception should have been thrown on negative month"); } catch (IllegalArgumentException e) { ; } }

That's a bit simpler, isn't it? By virtue of their construction, pseudos provide an inherent error-producing mechanism. By looking at the test, you can immediately tell that the test expects that the createMonthlyReport method will not operate on the Account object. How? Because if any code within the createMonthlyReport method calls any of the methods on the AccountPseudo, an exception will be thrown and the test will fail.

But that's not the only thing you can do with pseudo objects. As I mentioned earlier, you can also use a pseudo object to create a stub that returns canned data by extending the pseudo class and overriding its methods.

This unit test creates a stub by extending the AccountPseudo class and providing only the functionality needed for the test:

public void testCreateMonthlyReport() { //setup Account account = new AccountPseudo() { public double getBalance() { return 55.5; } }; int month = 2; int year = 1999; //execute Report actualReport = ReportFactory.createMonthlyReport(month, year, account); //test Report expectedReport = createExpectedReport(); assertEquals(expectedReport, actualReport); }

The stub is created by overriding AccountPseudo using an anonymous inner class that exists directly in the test method. You could use a named inner class or a top-level class just the same, but using an anonymous inner class is more compact. In fact, the inline stub makes the test more self-contained and explicit.

Another advantage to using inline stubs is that you can quickly detect when your test, and in turn your code, is doing too much. If your anonymous inner class starts growing too big, that is a pretty good indication that you should consider doing a little refactoring in order to separate functional responsibility of the method that you're testing.

For example, a method may be responsible for creating objects and then acting on those objects. These responsibilities could be split into two fine-grain methods, one factory method and another that operates on the objects. Fine-grain methods are more easily tested and are more apt to be reused. This is generally considered better from a design perspective.

You can use both stubs and mocks in your code. I lean more towards state-based unit-testing and thus use stubs almost exclusively. There are many people who use mock object libraries to create their stubs. I find it easier to use stubs that extend from a pseudo class. This also deters me from using the mock methods to test the implementation details of my methods. If I am working on code that uses mocks and it is simpler to use stubs, I usually do "replace mock with stub" refactoring to get rid of the mock.

If you are doing a mixture of interaction and state-based unit testing, one thing to remember is that mocks can actually extend pseudos the same way that stubs do. This strategy works well if you are incrementally developing your own custom mock objects, and only mocking methods that are on the interface as they are needed by your tests. But, this is not very beneficial if you are auto-generating your mock objects with a code-generation tool.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date