Click here to Skip to main content
Click here to Skip to main content

Using PowerMockito to Mock Final and Static Methods in Java Unit Testing

, 21 Jan 2015 CPOL
Rate this:
Please Sign up or sign in to vote.
This document presents two Maven example projects for mocking final and static methods using PowerMockito for Java unit testing.

Download JUnit Example
Download TestNG Example

Introduction

This document presents two Maven example projects for mocking final and static methods using PowerMockito for Java unit testing. One project is for JUnit, the other project is for TestNG.

Background

When writing a unit test, we may constantly need to mock certain classes, so we do not need to go through all the full running mechanism to test the code. According to the stack-overflow discussion, Mockito is a highly regarded mocking framework. The problem though is that Mockito by itself does not have the ability to mock final and static methods. If we want to mock these methods, we will need to use PowerMock with PowerMockito. Unfortunately the documentation on how to use PowerMockito is not very detailed. I had to go through quite some try-and-error to make it to work.

  • Most of the examples to use PowerMock skip the package "import" section in the Java code. I felt that I had difficulty to figure out which package the classes used in the code belong to;
  • There are two commonly used unit test environments in Java, JUnit and TestNG. In the different unit test environments, we need to use PowerMock differently.

The examples in this document will keep a record for me and possibly save some time for the people who are also interested in this subject. PowerMock can be used with either EasyMock or Mockito. This document only shows the usage of PowerMockito.

Install Maven

The attached examples are Maven projects. We need to have Maven to run them. If you do not have Maven on your computer, you will need to install it. Maven is a Java application, you will need to have a JDK and a JRE is not sufficient. The Java version used in my testing is "jdk1.7.0_60". After installing the JDK, you can go the Maven website to download Maven. The Maven version used in my testing is "3.2.1". According to the documentation, we will need to go through the following steps to complete the Maven installation on a Windows computer.

  • Unzip the downloaded zip file to a directory where you want to install Maven;
  • Add M2_HOME to the environment variables and set it to the directory where the Maven files are located. In my case, it is C:\Maven;
  • Add the M2 environment variable with the value %M2_HOME%\bin;
  • Append %M2% to the Path environment variable;
  • Make sure that JAVA_HOME exists in the environment variables and it is set to the location of the desired JDK. In my case, it is C:\Program Files\Java\jdk1.7.0_60;
  • Make sure that %JAVA_HOME%\bin is in the Path environment variable.

In the Windows environment, many people may be confused about the difference between the user environment variables and the system environment variables. The differences are the following,

  • The system variables are available to all the users, but the user variables are only available to the user who is currently logged in to the computer;
  • If the same environment variable (except the Path environment variable) is defined in both the user variable and the system variable sections, the value in the user section overrides the value in the system section;
  • If the Path environment variable is defined in both the user variable and the system variable sections, the effective Path will be the result of appending them together.

After completing all the steps, we can open the Command Prompt Window and type in the following Maven command,

mvn --version

We should see the following result, indicating that the Maven installation should have been successful.

The Final and Static Classes to Mock

In order to demonstrate PowerMockito's ability to mock final and static methods, I created the following simple classes.

package org.song.example;

public final class AFinalClass {
    public final String echoString(String s) {
        return s;
    }
}
package org.song.example;

public class AStaticClass {
    public static final String echoString(String s) {
        return s;
    }
}

Each class implements a method called "echoString()". I will be using these two methods to demonstrate how to mock them in the unit test programs using PowerMockito.

The Maven Dependencies

This document comes with two example Maven projects. One is for JUnit and the other is for TestNG. If we want to run the unit tests with JUnit, we will need to declare the following dependencies:

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.5.5</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>1.5.5</version>
        <exclusions>
            <exclusion>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-core</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-reflect</artifactId>
            </exclusion>
        </exclusions>
        <scope>test</scope>
    </dependency>

If we want to run the unit tests with TestNG, we will need to declare the following dependencies:

    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.8.8</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.5.5</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-testng</artifactId>
        <version>1.5.5</version>
        <exclusions>
            <exclusion>
                <groupId>org.testng</groupId>
                <artifactId>testng</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-core</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-reflect</artifactId>
            </exclusion>
        </exclusions>
        <scope>test</scope>
    </dependency>

We may need to pay some attention on the following issues on the Maven dependencies:

  • According to the Maven documentation, the TestNG package declared JUnit as its dependency, but it is declared as an "optional" dependency. Maven will not automatically add optional dependencies to its classpth. This is the case for this TestNG example, where JUnit is not needed;
  • In many cases, if the Maven packages are well defined, Maven can handle the transitive dependencies nicely. But it is always better to make sure exactly what packages are added to the Maven classpath. This is the reason why package exclusions are used in the dependency declaration of the "powermock-module-junit4" and "powermock-module-testng" packages to avoid potential version inconsistencies and confusions.

The Test Classes

If we want to use PowerMockito with JUnit, we will need to write the unit test classes like the following:

package org.song.example;
    
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
    
@RunWith(PowerMockRunner.class)
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest {
    
    @Test
    public void mockFinalClassTest() {
        AFinalClass tested = PowerMockito.mock(AFinalClass.class);
        
        final String testInput = "A test input";
        final String mockedResult = "Mocked final echo result - " + testInput;
        Mockito.when(tested.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(tested.echoString(testInput), mockedResult);
    }
    
    @Test
    public void mockStaticClassTest() {
        PowerMockito.mockStatic(AStaticClass.class);
        
        final String testInput = "A test input";
        final String mockedResult = "Mocked static echo result - " + testInput;
        Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
    }
}
  • Add annotation "@RunWith(PowerMockRunner.class)" to the test class;
  • Add annotation "@PrepareForTest({AFinalClass.class, AStaticClass.class})" to the test class, where the "AFinalClass" and "AStaticClass" are the classes being tested.

If we want to run the unit tests with TestNG, we will need to write the test classes like the following:

package org.song.example;
    
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
    
    
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest extends PowerMockTestCase {
    
    @Test
    public void mockFinalClassTest() {
        AFinalClass tested = PowerMockito.mock(AFinalClass.class);
        
        final String testInput = "A test input";
        final String mockedResult = "Mocked final echo result - " + testInput;
        Mockito.when(tested.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(tested.echoString(testInput), mockedResult);
    }
    
    @Test
    public void mockStaticClassTest() {
        PowerMockito.mockStatic(AStaticClass.class);
        
        final String testInput = "A test input";
        final String mockedResult = "Mocked static echo result - " + testInput;
        Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
    }
}
  • Add annotation "@PrepareForTest({AFinalClass.class, AStaticClass.class})" to the test class, where the "AFinalClass" and "AStaticClass" are the classes being tested;
  • The test class needs to extend the "PowerMockTestCase" class. According to the PowerMockito documentations, extending the "PowerMockTestCase" class is just one of the options to make the test class to work, but it also mentioned that extending the "PowerMockTestCase" class is the "safe" option.

Run the Unit Tests

If you want to run the example projects, you can download the attached zip files and unzip them to your desired folder. The attached projects are simple standard Maven projects. After unzipping them, you will see the standard "src" folder and the "pom.xml" file. You can open the Command Prompt Window and go to the folder that has the "pom.xml" file. You can then issue the following command:

mvn clean test

The following picture shows that the mocking of the final and static methods is successful in the JUnit testing environment.

If a project declares only JUnit or TestNG dependency but not both, Maven will use the declared unit test environment to run the tests. This is the case for the attached simple examples. In more complex projects, if both test environments are declared, you will need to make sure the desired unit test environment is used.

Additional Notes

PowerMockito and Mockito Work Together

In the test programs, it is not uncommon that some test cases have final or static methods to mock, while the others do not. It is important that we do not extend the "PowerMockTestCase" class if the test cases do not have final or static methods to mock. TestNG will use different object factory to create the test case instances in the two cases. If we extend the "PowerMockTestCase" class when there is no final nor static methods to work with, the unit tests will not run consistently under Surefire in Maven.

The Scope of the Final and Static Mocks

It is very common that in the same test class, we have more than one test methods. Let us take a look at the following test class.

package org.song.example;
    
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
    
    
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest extends PowerMockTestCase {
    private AFinalClass aFinalClass_mock = null;
    
    @Test
    public void mockFinalClassTest() {
        final String testInput = "A test input";
        final String mockedResult = "Mocked final echo result - " + testInput;
        
        aFinalClass_mock = PowerMockito.mock(AFinalClass.class);
        Mockito.when(aFinalClass_mock.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
    }
    
    @Test(dependsOnMethods = {"mockFinalClassTest"})
    public void mockFinalClassTest_1() {
        final String testInput = "A test input";
        final String mockedResult = "Mocked final echo result - " + testInput;
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
    }
    
    @Test
    public void mockStaticClassTest() {
        PowerMockito.mockStatic(AStaticClass.class);
        
        final String testInput = "A test input";
        final String mockedResult = "Mocked static echo result - " + testInput;
        Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
    }
    
    @Test(dependsOnMethods = {"mockStaticClassTest"})
    public void mockStaticClassTest_1() {
        final String testInput = "A test input";
        final String mockedResult = "Mocked static echo result - " + testInput;
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
    }
}

In the above class the "dependsOnMethods" property of the "@Test" annotation tells the test framework to run "mockFinalClassTest()" before "mockFinalClassTest_1()" and "mockStaticClassTest()" before "mockStaticClassTest_1()". If we run the test, we will get the following.

  • Both the "mockFinalClassTest_1()" and "ockStaticClassTest_1()" methods failed on the assertion;
  • This is because the scope of the mocks is limited to the test method where they are specified. 

The Scope of Regular Mockito Mocks

If a method is neither final nor static, we can simply use Mockito to mock it. The scope of the mock is different from the mocks for final and static methods. Let us take a look at the following example.

package org.song.example;
    
public class RegularClass {
    public String Echo(String s) {
        return s;
    }
}

The "RegularClass" is the class to be tested, and the following is the unit test class.

package org.song.example;
    
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;
    
public class RegularTest {
    private RegularClass instance = null;
    
    @Test
    public void test1() {
        final String expected = "Expected String";
        instance = Mockito.mock(RegularClass.class);
        Mockito.doReturn(expected).when(instance).Echo(Mockito.anyString());
        
        Assert.assertEquals(instance.Echo("Song"), expected);
    }
    
    @Test(dependsOnMethods = {"test1"})
    public void test2() {
        final String expected = "Expected String";
        
        Assert.assertEquals(instance.Echo("Song"), expected);
    }
}
  • The "test1" method initiated a Mockito mock for the "RegularClass" and assigned it to the instance variable "instance";
  • The "test2" simply uses the "instance" for the testing without re-initiating the mock.

If we run the unit test, we can see that both test methods run successfully.

This experiment shows us that the scope of the mocks created by regular Mockito goes beyond the limit of the test method where the mock is created, which is different from the scope of the mocks on final and static method created by PowerMockito.

Mock Object Construction (new) with PowerMockito

One of the challenges of the unit testing is to mock the locally created objects. There are many discussions on how to make the code more unit-testable by applying some desired design patterns and if we should use dependency injections. Despite these good design patterns, PowerMockito does have the ability to mock locally created objects. Let's take a look at the following two classes.

package org.song.example;
    
import java.util.Random;
    
public class ScoreGrader {
    public int getScore() {
        Random random = new Random();
        int score = 60 + (int) Math.round(40.0 * random.nextDouble());
        
        return score;
    }
}
package org.song.example;
    
public class Student {
    public int getMathScore() {
        ScoreGrader grader = new ScoreGrader();
        
        return grader.getScore();
    }
}
  • The "ScoreGrader" class implemented a method "getScore" that returns a randomly generated integer between 60 - 100;
  • The "getMathScore" method in the "Student" class instantiated a "ScoreGrader" object and used it to generate the math score for the student.

The following is the unit test for the "Student" class that mocked the object construction (new) of the "ScoreGrader" class.

package org.song.example;
    
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
    
@PrepareForTest({Student.class})
public class MockConstructionTest extends PowerMockTestCase {
    
    @Test
    public void mockConstruction() {
        Student student = new Student();
        
        final int expectedScore = 120;
        
        ScoreGrader grader_mock = Mockito.mock(ScoreGrader.class);
        Mockito.doReturn(expectedScore).when(grader_mock).getScore();
        try {
            PowerMockito.whenNew(ScoreGrader.class)
                .withNoArguments().thenReturn(grader_mock);
        } catch (Exception e) {
            Assert.fail("Unable to mock the construction of "
                    + "the ScoreGrader object");
        }
        
        // This assertion will succeed because the mock is used to 
        // generate the score, so a score greater than 100 is generated
        Assert.assertEquals(student.getMathScore(), expectedScore);
    }
}

 

  • In the "@PrepareForTest" annotation, we need to specify the class where the "new" object construction happens, not the class being constructed;
  • The call to the method "PowerMockito.whenNew()" can alter the object construction, so the construction process can return an object mock to the caller.

If we run the test, we will find that it succeeds. This indicates that the mock is obtained when the "ScoreGrader grader = new ScoreGrader();" statement is issued, because a true "ScoreGrader" object can never generate a score larger than 100.

Mock Singleton

package org.song.example;
    
import java.util.Random;
    
public class SingletonScoreGrader {
    private static SingletonScoreGrader instance = null;
    
    private SingletonScoreGrader() {}
    
    public static synchronized SingletonScoreGrader instance() {
        if (instance == null) {
            instance = new SingletonScoreGrader();
        }
        
        return instance;
    }
    
    public int getScore() {
        Random random = new Random();
        int score = 60 + (int) Math.round(40.0 * random.nextDouble());
        
        return score;
    }
}
  • The "SingletonScoreGrader" class is a typical singleton class;
  • The "instance" method returns the single instance of the "SingletonScoreGrader" object.
package org.song.example;
    
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
    
@PrepareForTest({SingletonScoreGrader.class})
public class MockSingletonTest extends PowerMockTestCase {
    
    @Test
    public void mockSingleton() {
        final int expectedScore = 120;
        
        PowerMockito.mockStatic(SingletonScoreGrader.class);
        SingletonScoreGrader singletonMock = Mockito.mock(SingletonScoreGrader.class);
        Mockito.doReturn(expectedScore).when(singletonMock).getScore();
        
        Mockito.when(SingletonScoreGrader.instance()).thenReturn(singletonMock);
        
        
        Assert.assertEquals(SingletonScoreGrader.instance().getScore(), expectedScore);
    }
}

 

To mock the singleton class we can simply create a mock of the class and mock the static "instance" method to return the mock. Running the above test, we will find it finishes successfully.

Besides "Mockito.doReturn()", let's "Mockito.doAnswer()"

When using Mockito, we can use "Mockito.doReturn()" to mock the behavior of a method and alter the object returned by the method. While it is very easy to use, the "Mockito.doAnswer()" method is much more powerful.

package org.song.example;
    
import java.util.ArrayList;
import java.util.List;
    
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
    
@PrepareForTest({AFinalClass.class})
public class DoAnswerTest extends PowerMockTestCase {
    
    @Test
    public void doAnswerTest() {
        final String testInput = "A test input";
        final String mockedResult = "Mocked final echo result - " + testInput;
        
        AFinalClass aFinalClass_mock = PowerMockito.mock(AFinalClass.class);
        
        final List<String> answers = new ArrayList<String>();
        final String mockIsUsed = "The mock is used to generate the result";
        
        Mockito.doAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                answers.add(mockIsUsed);
                return mockedResult;
            }
        }).when(aFinalClass_mock).echoString(testInput);
        
        // Assert the mocked result is returned from method call
        Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
        
        // In addition to the mocked result, we can keep additional information of
        // the calling of the mock and add complex logic in the mock
        Assert.assertEquals(answers.size(), 1);
        Assert.assertEquals(answers.get(0), mockIsUsed);
    }
}
  • The "Mockito.doAnswer()" method has the same capability as "Mockito.doReturn()" to specify the expected mocked result returned by a method;
  • It is more powerful because it allows us to define a true method to control the generation of the mocked result and keep the calling history of the mocked method.

Running the above test, we can see the following result.

Test Listeners with Surefire

This topic is not directly related to Mockito, but it may be helpful when doing the unit tests. It is not uncommon that we may want to perform some actions, such as initializing a test database, before any unit test case to start. If these actions fail, we want to fail and quit the unit test with failure. This can be easily done with Surefire listeners. We can configure the listeners in the POM like the following:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${surefire.version}</version>
    
    <configuration>
        <properties>
            <property>
                <name>listener</name>
                <value>org.song.example.MyRunListener</value>
            </property>
        </properties>
    
        <systemPropertyVariables>
            <P1>P1 value</P1>
            <P2>P2 value</P2>
        </systemPropertyVariables>
    </configuration>
</plugin>

Besides adding a listener, we also added some "systemPropertyVariables" to the Surefire configuration. The "MyRunListener" is implemented as the following:

package org.song.example;
    
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
    
public class MyRunListener implements ITestListener {
    public void onTestStart(ITestResult result) {}
    public void onTestSuccess(ITestResult result) {}
    public void onTestFailure(ITestResult result) {}
    public void onTestSkipped(ITestResult result) {}
    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {}
    public void onStart(ITestContext context) {
        System.out.println("Test Started globally - "
                +  System.getProperty("P1") + " - " + System.getProperty("P2"));
        Assert.fail("Artificially failed the test");
    }
    public void onFinish(ITestContext context) {}
}

The "MyRunListener" implements the "ITestListener" interface, which defines many events for the listener to listen to, but the "MyRunListener" only takes action on the "onStart" event for simplicity. After running the unit test, we can see the following result.

  • The values configured in the "systemPropertyVariables" section are printed out by the code;
  • The unit test failed because we asserted an artificial failure. As expected, this failure also quit the unit test before any test case to start.

Suppressing Unwanted Behavior and WhiteBox

Instead of making my own examples, let me simply add the links to the references here. When mocking some objects, we may find the following links are useful and sometimes critical.

Proper Way to Extend "PowerMockTestCase"

According to the PowerMock documentation, the "safe" way to tell TestNG to use PowerMock is to let the test classes to extend the "PowerMockTestCase" class from the "org.powermock.modules.testng" package. It work fine but there is a trick. What I have learned from my experience is that we should never let an abstract class to extend the "PowerMockTestCase". If we do it, the TestNG may fail, and the Surefire will not tell us what exactly going on and way.

Points of Interest

  • This document presented two running Maven example projects for mocking final and static methods using PowerMockito in Java unit testing;
  • PowerMock can be used with either EasyMock or Mockito. This document only shows the usage of PowerMockito;
  • There are some discussions to avoid final and static methods in the Java code to make it more unit testable. But sometimes, we may need to mock third-party Java code and we do not have the options to bypass the final and static methods, plus the final and static methods do have their indispensable values in the Object-oriented principles;
  • I hope you like my postings and I hope this article can help you one way or the other.

History

First Revision - 8/14/2014, Revised - 9/8/2014

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dr. Song Li

United States United States
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.

Comments and Discussions

 
GeneralMy vote of 5 PinprofessionalPrasad Khandekar23-Jan-15 21:30 
GeneralMy vote of 5 PinprofessionalVolynsky Alex7-Jan-15 13:24 
GeneralAppreciated. PinmemberOsmund Francis15-Aug-14 21:48 
QuestionPrivate and static methods PinmemberSeb Carss15-Aug-14 10:35 
AnswerRe: Private and static methods PinmvpDr. Song Li15-Aug-14 10:55 
QuestionHow does it work? PinprofessionalVinicius G. D. Menezes15-Aug-14 5:19 
AnswerRe: How does it work? PinmvpDr. Song Li15-Aug-14 6:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150123.1 | Last Updated 21 Jan 2015
Article Copyright 2014 by Dr. Song Li
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid