6

I have been having a query regarding writing unit tests for web methods which actually communicates with a database and returns some value.

Say for example I have a web service named "StudentInfoService". That web serivces provides a API "getStudentInfo(studentid)"

Here is some sample snippet

public class StudentInfoService
{
    public StudentInfo getStudentInfo(long studentId) {
            //Communicates with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

How do we actually write unit tests for this method getStudentInfo? Generally how do we write unit tests for methods which involves a connection with a resource(Database, Files, JNDI, etc...)?

4 Answers 4

4

Firstly, the class StudentInfoService in your example is not testable, or atleast not easily. This is for a very simple reason - there is no way to pass in a database connection object to the class, at least not in the method that you've listed.

Making the class testable would require you to build your class in the following manner:

public class StudentInfoService
{
    private Connection conn;

    public StudentInfoService(Connection conn)
    {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo(long studentId) {
            //Uses the conn object to communicate with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

The above code allows for dependency injection via the constructor. You may use setter injection instead of constructor injection, if that is more suitable, but it usually isn't for DAO/Repository classes, as the class cannot be considered fully formed, without a connection.

Dependency injection would allow your test cases to create a connection to a database (which is a collaborator to your class/system under test) instead of getting the class/system itself to create the collaborator objects. In simpler words, you are decoupling the mechanism of establishing database connections from your class. If your class was previously looking up a JNDI datasource and then creating a connection, then it would have been untestable, unless you deployed it to a container using Apache Cactus or a similar framework like Arquillian, or if you used an embedded container. By isolating the concern of creating the connection from the class, you are now free to create connections in your unit tests outside the class and provide them to the class on a as-needed basis, allowing you to run tests inside a Java SE environment.

This would enable you to use a database-oriented unit testing framework like DbUnit, which would allow you to setup the database in a known state before every test, then pass in the connection to the StudentInfoService class, and then assert the state of the class (and also the collaborator, i.e. the database) after the test.

It must be emphasized that when you unit test your classes, your classes alone must be the only systems under test. Items like connections and datasources are mere collaborators that could and ought to be mocked. Some unit tests would use in-memory databases like H2, HSQL, or Derby for unit-tests and use the production-equivalent installations of databases for integration and functional testing.

Sign up to request clarification or add additional context in comments.

7 Comments

No, your service or DAO whatever does't necessarily have to expose the connection to be tested. The connection (or better, the DataSource), could be found with a classpath resource, and the test could find the same resource with the classpath either. The only advantage of doing so, is to mock the connection, and test what is called on it, but for DB tests, it is better to test for real the database layer (for example to test the SQL syntax).
@Bruno, classes being unit-tested should be amenable to have their collaborators passed/injected into them. Not doing so, will end up requiring integration tests to test these classes. Your statement of requiring a lookup of a classpath resource itself suggests that a test would end up being an integration test, and would also end up testing two properties of the class-under-test; it would test the relevant business logic, and also whether the resource can be found in the classpath. That in my opinion is a poorly written unit test. There are better options for an integration test.
yes that's what I meant : for database layer, it is better to test with the database. First, to test the DB layer (SQL), second to allow the design to evolve more easily because mocking is freezing the design into the tests, so when you want to refactor, your tests are another obstacle... what you definitly don't need.
@Bruno, I think our points are the same, but we are differing on how do we achieve them. My point is about allowing injection of the database connection, to aid in test setup so that connections can be created outside the class so that the class avoids dual responsibilities, and also makes it more testable in a Java SE environment. As far as testing whether the correct SQL statements are issued, that is made easier via the use of DbUnit which can be used to verify if the contents of the database are in a correct state after an INSERT/DELETE/UPDATE, or via the SUT for SELECTs.
@Bruno, On the topic of refactoring and tests getting in the way, I would disagree, because the tests are there to ensure that the object model stays in sync with the data model. If you do not write tests to verify if these are in sync after every refactoring, then you will find out only at the point of integration testing, which from my experience is quite late for large object and data models.
|
2

Try to use http://www.dbunit.org/intro.html.

Main idea - make a stub database with known dataset to run your tests and assert results. You will need to reload the dataset before runs to restore initial state.

Comments

1

We are using the in-memory HSQL database. It is very fast and SQL-92 compliant. In order to make our PostgreSQL queries run on HSQL, we rewrite the queries using a self-written test SessionFactory (Hibernate). Advantages over a real database are:

  1. much faster, which is important for unit tests
  2. requires no configuration
  3. runs everywhere, including our continuous integration server

Comments

0

When working with "legacy code", it can be difficult to write unit tests without some level of refactoring. When writing objects, I try to adhere to SOLID. As part of SOLID, the "D" stands for dependency inversion.

The problem with legacy code is that you may already have numerous clients that are using the no arg constructor of StudentInfoService, which can make adding a constructor that takes a Connection conn parameter difficult.

What I would suggest isn't generally best practice, because you're exposing test code in your production system, but it is sometimes optimal for working with legacy code.

public class StudentInfoService {
    private final Connection conn;

    /**
     * This no arg constructor will automatically establish a connection for you. This
     *  will remain around to support legacy code that depends on a no arg constructor.
     */
    public StudentInfoService() throws Exception {
        conn = new ConcreteConnectionObject( ... );
    }

    /**
     * This constructor may be used by your unit tests (or new code).
     */
    public StudentInfoService( Connection conn ) {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo() {
        // this method will need to be slightly refactored to use
        // the class variable "conn" instead of establishing its own connection inline.
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.