| Unit Testing with TestNG and jmockit |
|
|
|
To begin, complete my Unit testing with JUnit and EasyMock tutorial. Then run TestNG's JUnit converter. That's it. Tutorial over. Just kidding (although TestNG does have a converter that you can run to convert existing JUnit tests to TestNG tests). SetupAlthough TestNG does provide a JUnit conversion tool, that is not the approach we will take here. So let's get started. I will be using Eclipse 3.3 Europa to do this tutorial. To begin, create a new java project and call it TestNGTutorial. Right click on your new project and select New --> Folder. Name it lib and click Finish. Usually you don't want to package your test code with your regular code, so let's make an additional source directory, test. To do that, right click on your new project and select Source Folder. Next we need to add TestNG to our build path. To make our life easier and since we are serious about unit testing ;) we will use the TestNG plugin for Eclipse. It includes a test runner and other functionality that will make things easier for test development. To install the plugin, in Eclipse, go to Help --> Software Updates --> Find and Install... From there, select "Search for new feature to install" and click "Next>". In the upper right hand corner, click on "New Remote Site". Enter "TestNG Plugin" as the Name and http://beust.com/eclipse as the URL and click OK. Make sure TestNG is the only thing with a check next to it and click Finish. In the new window that is displayed, check the box next to testng and click Next>. On the following screen, accept the license agreement and click Next>. Click Finish. Once the download is complete, Eclipse will ask you what to install. Click Install All. Once the install is complete, restart Eclipse. Once Eclipse is back, right click on your project and click Properties. Select Java Build Path from the left and click on the Libraries tab. On the right, click Add Variable and select TESTNG_HOME. Click the Extend... button to the right and select /lib/testng-jdk15.jar and click Ok. Click OK to exit the Properties window. We now need to get jmockit. This is the mock framework we will be using for this tutorial. To download jmockit, go to here and download the JMockit 0.94c release. Once the download is complete, extract the jmockit.jar file from the archive and place it in the lib folder we created previously. To add the new jar to your classpath, right click on your project and select Properties. Go to Java Build Path and select the Libraries tab. From there, click on Add JARs.... Select the jmockit jar we just copied into the lib directory and click Ok. Click Ok to exit the properties window. You are now ready to code! The scenarioIn test driven design, we develop the unit test before the functionality. We write a test that verifies that the method should do X after our call. We prove that the test fails, we then create the component to make the test pass. In this case, we are going to create a service with a method that authenticates a user. Below is a class diagram of the scenario.
The interfacesAs stated before, we will be testing the same scenario as we did in the previous tutorial. To review, we will start our coding by defining two interfaces, LoginService and UserDAO We will implement LoginService, however since in this tutorial UserDAO will be mocked, we won't bother implementing it right now. For LoginService, we have a single method that takes a String userName and String password and returns a boolean (true if the user was found, false if it was not). The interface looks like this:
/**
* Provides authenticated related processing.
*/
public interface LoginService {
/**
* Handles a request to login. Passwords are stored as an MD5 Hash in
* this system. The login service creates a hash based on the paramters
* received and looks up the user. If a user with the same userName and
* password hash are found, true is returned, else false is returned.
*
* @parameter userName
* @parameter password
* @return boolean
*/
boolean login(String userName, String password);
}
The UserDAO interface will look very similar to the LoginService. It will have a single method that takes a userName and hash. The hash is an MD5 hashed version of the password, provided by the above service.
/**
* Provides database access for login related functions
*/
public interface UserDAO {
/**
* Loads a
TestNGBefore we begin development, we will develop our test. Tests are structured by grouping methods that perform a test together in a test case. TestNG subscribes well to POJO development styles by not requiring you extend any class or implement any interface. The test lifecycle is directed via annotations (we will be covering standard JDK 1.5+ annotations in this tutorial, TestNG does support JDK 1.4 as well. It requires an additional JAR file and uses XDoclet style annotations). The annotations we will cover in this tutorial are:
The method we will be testing is the jmockit
Most mocking frameworks (JMock, EasyMock, etc) are based on the
jmockit addresses these two issues using the redefinition ability. If you are unfamiliar with the new ability to redefine classes in JDK 1.5+, essentially what it does is it allows you to programatically tell the JVM to remap a class to a different class file. So when you create a mock with jmockit, you will be creating a new class that the JVM will replace the dependent class with. The test caseSo now that we know a tiny bit about both TestNG and jmockit, let's start to flush out our test case. To start, well create an empty TestNG testcase with the appropriate imports:
import org.testng.annotations.*;
public class LoginServiceTest {
}
Note that the class does not extend any class or implement any interface. This is because TestNG does not require it. We will dictate all of the lifecycle stages using annotations. As we noted before, the
import mockit.Expectations;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
public class LoginServiceTest extends Expectations {
private LoginServiceImpl service;
UserDAO mockDao;
@BeforeMethod
public void setupMocks() {
service = new LoginServiceImpl();
service.setUserDao( mockDao );
}
}
Let's go over what we have in the above class. We have the two required imports for this test case,
Next we have our The next step is to create our actual test method. In our test method, we want to test the following scenario:
Even with the very basic method we want to test above, there are still a number of different scenarios that require tests. We will start with the "rosy" scenario, passing in two values and getting a user object back. Below is the updated test with our new test method.
...
/**
* This method will test the "rosy" scenario of passing a valid
* username and password and retrieveing the user. Once the user
* is returned to the service, the service will return true to
* the caller.
*/
@Test
public void testRosyScenario() {
User results = new User();
String userName = "testUserName";
String password = "testPassword";
String passwordHash =
"�Ӷ&I7���Ni=.";
invokeReturning(
mockDao.loadByUsernameAndPassword( userName,
passwordHash ),
results );
endRecording();
assert service.login( userName, password ) :
"Expected true, but was false";
}
...
So let's go thru the code above. We start out with our annotation Once declaring the various variables, the next call:
invokeReturning(
mockDao.loadByUsernameAndPassword( userName, passwordHash ),
results );
tells jmockit to expect the method endRecording();
tells jmockit that we are done recording the expectations for this test case. jmockit automatically goes into replay mode at this point. Finally we perform the actual "test" by executing the method to be tested, To run our test case, we have one more thing to configure. We need to tell the JVM that we will have a java agent running. To do that, right click on the LoginServiceTest, select Run As --> Open Run Dialog. On the left, select TestNG. At the top, click on the new icon. In the new area to the right, Enter LoginServiceTest in the Class box. Click the Arguments tab at the top. In the VM Arguments box, enter the following (where <PATH_TO_LIB> is the fully qualified path to the lib folder of your project): -javaagent:<PATH_TO_LIB>/jmockit.jar All that is left is to click Run. What? The test failed?! That's because we haven't implemented the method we are testing yet. The code for that is below.
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class LoginServiceImpl implements LoginService {
UserDAO userDao;
public void setUserDao(UserDAO userDao) {
this.userDao = userDao;
}
public boolean login(String userName, String password) {
boolean valid = false;
try {
String passwordHash = null;
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
passwordHash = new String(md5.digest());
User results =
userDao.loadByUsernameAndPassword(userName, passwordHash);
if(results != null) {
valid = true;
}
} catch (NoSuchAlgorithmException ignore) {}
return valid;
}
}
ConclusionAs you can see, TestNG provides a robust featureset that isn't foreign to someone who is used to JUnit styles of unit tests. In part two of this tutorial, we will refactor the LoginService to take better advantage of the jmockit framework and create additional tests that we can organize with TestNG. Until next time! 6 ResponsesAdd your own comment... |
| < Prev | Next > |
|---|
its good
hi,
anybody to help,hwo we can use jmockit for classes in different packages,i.e when the class to be mocked uses methods of different class of different module
thanks
shashi
hi,
won't work for me, some information is missing in this tutorial. invokeReturning() and endRecording() are undefined. Sample code like in your previous tutorial would be nice to track down what i am missing.
-dan
hi, it was good
This is a great tutorial. I just wanted to mention that if you download and use the latest version of jmockit it won't work. You need to use the version which the author mentions. There are a couple methods which have been deprecated (i.e. invokeReturning). Also the structure of the test case has changed and you no longer extend your test case with Expectations. Otherwise, a great example!
Cheers!
~MattS
I would appreciate if the source was listed as a download like some of your other tutorials. I am trying to learn how to use unit testing and mocking and when i have issues in a tutorial, it really helps if I have reference code to look through to see what mistake I am making.