Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

When creating unit tests for those other objects, what is the best way to create mock objects that provide data to other objects. Should they be created at a 'high level' and intercept the calls as soon as possible, or should they be done at a 'low level' and so make as much as the real code still be called?

e.g. I'm writing a test for some code that requires a NoteMapper object that allows Notes to be loaded from the DB.

class NoteMapper {

    function getNote($sqlQueryFactory, $noteID) {
        // Create an SQL query from $sqlQueryFactory
        // Run that SQL 
        // if null 
        //     return null
        // else
        //    return new Note($dataFromSQLQuery)
    }
}

I could either mock this object at a high level by creating a mock NoteMapper object, so that there are no calls to the SQL at all e.g.

class MockNoteMapper {

    function getNote($sqlQueryFactory, $noteID) {
        //$mockData = {'Test Note title', "Test note text" }
        // return new Note($mockData);
    }
}

Or I could do it at a very low level, by creating a MockSQLQueryFactory that instead of actually querying the database just provides mock data back, and passing that to the current NoteMapper object.

It seems that creating mocks at a high level would be easier in the short term, but that in the long term doing it at a low level would be more powerful and possibly allow more automation of tests e.g. by recording data in an out of a DB and then replaying that data for tests.

Is there a recommended way of creating mocks? Are there any hard and fast rules about which are better, or should they both be used where appropriate?

share|improve this question

5 Answers 5

Higher level approaches are more appropriate for unit testing. Lower-level approaches are more appropriate for integration testing.

The whole point of a unit test is to make sure that single unit works, so you want to remove the possibility of a test failing due to another component's bug. The notion that lower-level testing is more "automatic" is false. Unit tests could still draw upon an external data source for fixtures. The level of mocking and testing is what makes a unit test, not the data source.

Imagine for a minute that you're creating a component to transform a piece of text that comes from a database. Something like public String removeSpaces(Database db). You could store some test text in file A and the expected outcome in file B. Your test case would then have to mock db return the contents of A. Once you've called the method you're testing, you can compare the returned value to the contents of B. Your data is external and editable without recompiling. You could even be cool and use a file naming convention so that the reading of A and B is simpler.

The lower-level testing is nice for integration testing. They are powerful, but they also serve a different purpose. I'll leave that topic alone since the question is primarily about unit testing.

share|improve this answer

Both.

When testing objects that use NoteMapper, you can use its mock to avoid calls to SQL, and tell it to do whatever you want with complete freedom in order to test how the SUT responds to it.

Additionally, when testing NoteMapper dependencies, if you'd define expectations on the inner SQL object NoteMapper uses, you're coupling to its implementation tightly, and also creating a lot of boilerplate in each test about things it really shouldn't care about.

When testing NoteMapper itself, you might want to mock out the SQL so that you're testing only the logic and not the actual SQL implementation.

However, I believe that NoteMapper would turn out to not do much besides SQL queries, and so its test should actually be an integration test, testing the integration between its implementation and the particular SQL DB's implementation.

share|improve this answer

Code implementing unit tests is code that you need to develop, extend, fix and maintain and you are willing to do this correctly and to avoid useless work. If you plan a large unit test suite then its design deserves the same care and thoughts than the application itself: finding the right organisation is just as hard.

Besides this general statement, here are several aspects you may want to consider. I assume you are writing a unit test for a class Consumer that uses the NoteMapper (this is the low-mocking case) or MockNoteMapper (this is the high-mocking case).

  • In the low-mocking case you are actually testing Consumer and NoteMapper so you probably want to add a separate test NoteMapper separately so that your test is an evidence that Consumer implements the correct behaviour.

  • In the low-mocking case the NoteMapper may contains hidden invariants that are not enforced by your design but by the implementation. In the high-mocking case you gain confidence that your implementation of Consumer is correct to the point where it does not use hidden invariants of NoteMapper.

  • Unit tests based on mocking objects are implemented similarly to regression tests, where the mocking object is used to inject values demonstrating some historical bug in the program.

share|improve this answer

I would suggest the high-level approach. Mock as soon as possible.

This way you are sure what your method will receive and thus will be faster to trace the error. Remember: you want to isolate your unit test from everything else. If you would mock on the low level as you described you would test not only your method you want to test, but also getNote. This would resemble more an integration or acceptance test.

share|improve this answer

Unit tests are exactly that, to test a small unit of code. Based on your description of the problem, it sounds like a high-level mocked up data test and a low-level test will test different units of functionality, so, for complete coverage of your code, it would be wise to test both scenarios.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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