Unit testing JavaFx 2 application with TestNG - unit-testing

I wrote a rather complex JavaFx 2 application for which I'd like to write a bunch of unit tests. Problem is when I try to conduct the tests I get a runtime error complaining about uninitialized toolkit.
From what I can tell I should somehow invoke Application.launch() in a #BeforeClass method but this causes a deadlock as Application.launch() doesn't return to calling thread.
So question is how should I initialize JavaFx?
This is the skeleton of the code that doesn't work:
public class AppTest extends Application {
#BeforeClass
public void initialize() {
launch(); //this causes a deadlock
}
#Test
public void test1() {
//conduct test here
}
#Test
public void test2() {
//conduct other test here
}
#Override
public void start(Stage arg0) throws Exception {
}
Thanks in advance!

From another question here on stackoverflow, I've made myself this little helper class:
import javafx.application.Application;
import javafx.stage.Stage;
public class JavaFXInitializer extends Application {
private static Object barrier = new Object();
#Override
public void start(Stage primaryStage) throws Exception {
synchronized(barrier) {
barrier.notify();
}
}
public static void initialize() throws InterruptedException {
Thread t = new Thread("JavaFX Init Thread") {
public void run() {
Application.launch(JavaFXInitializer.class, new String[0]);
}
};
t.setDaemon(true);
t.start();
synchronized(barrier) {
barrier.wait();
}
}
}
which can then be used easily in a #BeforeClass setup method:
#BeforeClass
public void setup() throws InterruptedException {
JavaFXInitializer.initialize();
}

The main think is to consider your tests to be run inside an FX thread. When you create a class extends Application, you create in fact a process. This is what you want to test.
So to launch some unit tests on an Application, first create an FXAppTest that extends Application and then inside FXAppTest you launch your unit test. Here is the idea.
Here is an example with JUnit. I create a Runner that launch the test inside an FXApp for test.
Here is an example of code for FxApplicationTest (we launch unit test inside it)
public class FxApplicationTest extends Application {
private volatile boolean isStopped;
#Override
public void start(final Stage stage) {
StackPane root = new StackPane();
Scene scene = new Scene(root, 10, 10);
stage.setScene(scene);
}
public void startApp() {
launch();
}
public void execute(final BlockJUnit4ClassRunner runner, final RunNotifier notifier) throws InterruptedException {
isStopped = false;
Platform.runLater(new Runnable() {
#Override
public void run() {
runner.run(notifier);
isStopped = true;
}
});
while (!isStopped) {
Thread.sleep(100);
}
}
And the Runner :
import org.apache.log4j.Logger;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
public class JUnitFxRunner extends Runner {
private final BlockJUnit4ClassRunner runner;
private final Logger LOGGER = Logger.getLogger(JUnitFxRunner.class);
public JUnitFxRunner(final Class<?> klass) throws InitializationError {
super();
runner = new BlockJUnit4ClassRunner(klass);
}
#Override
public Description getDescription() {
return Description.EMPTY;
}
#Override
public void run(final RunNotifier notifier) {
try {
final FxApplicationTest fxApplicationTest = new FxApplicationTest();
MyTestRunner runnable = new MyTestRunner(runner, notifier, fxApplicationTest);
new Thread(runnable).start();
Thread.sleep(100);
runnable.execute();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
private class MyTestRunner implements Runnable {
private final BlockJUnit4ClassRunner runner;
private final RunNotifier notifier;
private final FxApplicationTest fxApp;
public MyTestRunner(final BlockJUnit4ClassRunner runner, final RunNotifier notifier, final FxApplicationTest fxApp) {
this.runner = runner;
this.notifier = notifier;
this.fxApp = fxApp;
}
#Override
public void run() {
fxApp.startApp();
}
public void execute() throws InterruptedException {
fxApp.execute(runner, notifier);
}
}
}
Now, simply launch test using the runner :
import fr.samarie_projects.fx.utils.JUnitFxRunner;
#RunWith(JUnitFxRunner.class)
public class MainFxAppTest {
#org.junit.Test
public void testName() throws Exception {
MainFxApp fxApp = new MainFxApp();
fxApp.start(new Stage());
}
}
This unit test MainFxApp
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MainFxApp extends Application {
#Override
public void start(final Stage stage) throws Exception {
StackPane root = new StackPane();
Scene scene = new Scene(root, 10, 10);
stage.setScene(scene);
}
public static void main(final String[] args) {
launch(args);
}
}
Sure, this code need to be reviewed. It is only to present the idea.

Well, considering that you might have your JavaFX app located at project-root/src/main/java/package/FXApp.java then you might have your tests located elsewhere like project-root/src/test/java/package/FXAppTest.java . This being the case, the FXAppTest class could call the FXApp class by initializing it using BeforeClass .
In theory you should be able to start your FX app with something like:
// imports located here that import junit 4.11+ packages (or TestNG)
public class FXAppTest {
#BeforeClass
public void initialize() {
FXApp fxa = new FXApp();
while ( fxa.isLoading() ) {
// do nothing
}
}
....
NOTE: Notice that FXAppTest does not extend Application here.
Now, if this doesn't clue you into the problem, you could enable JMX args on the JVM and then view the locked threads with JVisualVM.

Related

Unit testing RxJava in MVP presenter in android

I am new to TDD. Also new to MVP and Rxjava. I just dive into it and It is worth it. But I stuck at the testing part. I understand the basis of unit testing. It is a little bit difficult for me in beginning. But I stuck here and So how can test the presenter?
Here is the Presenter class -
public class NewsPresenter {
private final RxjavaService service;
private final MainView view;
private CompositeSubscription subscriptions;
public NewsPresenter(RxjavaService service, MainView view) {
this.service = service;
this.view = view;
subscriptions = new CompositeSubscription();
}
public void getNewsList(String urlQ){
view.showWait();
Subscription subscription = service.getNews(urlQ ,new RxjavaService.GetNewsCallback() {
#Override
public void onSuccess(Articles articles) {
view.removeWait();
view.getNewsListSuccess(articles);
}
#Override
public void onError(NetworkError networkError) {
view.removeWait();
view.onFailure(networkError.getAppErrorMessage());
Log.i("huh",networkError.getMessage());
}
});
subscriptions.add(subscription);
}
public void onStop(){
subscriptions.unsubscribe();
}
}
Here is the View Interface -
public interface MainView {
void showWait();
void removeWait();
void onFailure(String appErrorMessage);
void getNewsListSuccess(Articles articles);
}
Here is the RxJavaService class -
public class RxjavaService {
private final NewsRestService newsRestService;
public RxjavaService(NewsRestService newsRestService) {
this.newsRestService = newsRestService;
}
public interface GetNewsCallback {
void onSuccess(Articles articles);
void onError(NetworkError networkError);
}
public Subscription getNews(String q, final GetNewsCallback getNewsCallback) {
Log.i("stuck","service called");
return newsRestService.getNewsBySearch(q,"8dca7dea475e41e49518b2c61131e118",100)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Func1<Throwable, Observable<? extends Articles>>() {
#Override
public Observable<? extends Articles> call(Throwable throwable) {
return Observable.error(throwable);
}
})
.subscribe(new Subscriber<Articles>() {
#Override
public void onCompleted() {
Log.i("stuck","complete");
}
#Override
public void onError(Throwable e) {
getNewsCallback.onError(new NetworkError(e));
Log.i("stuck",e.getMessage());
}
#Override
public void onNext(Articles articles) {
getNewsCallback.onSuccess(articles);
Log.i("stuck","Onnext");
}
});
}
}
Here is the Test class where I am stuck-
#RunWith(MockitoJUnitRunner.class)
public class NewsListTest {
private NewsPresenter newsPresenter;
#Mock
private RxjavaService rxjavaService;
#Mock
private MainView mainView;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
newsPresenter = new NewsPresenter(rxjavaService,mainView);
}
#After
public void tearDown() throws Exception {
mainView = null;
newsPresenter.onStop();
}
#Test
public void Testing_The_Result() {
}
}
First things first
If you're into TDD, you'd never get into the situation you described. In TDD you start with a failing test, and just then go write the implementation. So your question is much more about testing then TDD.
I would recommend switching to RxJava2, as RxJava1 reaches End Of Life on March 31.
Look strange to me that RxJavaService changes the API from publish/subscribe to callbacks. Why not stick with rx API all the way to presenter?
Test with mocked RxJavaService
If you'd like to finish writing the test with the setup you have in the test, it would look something like this:
#Test
public void Testing_The_Result() {
final RxjavaService.GetNewsCallback[] callback = new RxjavaService.GetNewsCallback[1];
Mockito.when(rxjavaService.getNews(ArgumentMatchers.anyString(), ArgumentMatchers.any(RxjavaService.GetNewsCallback.class))).thenAnswer(new Answer<Subscription>() {
public Subscription answer(InvocationOnMock invocationOnMock) {
callback[0] = invocationOnMock.getArgument(1);
return mock(Subscription.class);
}
});
newsPresenter.getNewsList("some url");
Articles articles = new Articles();
callback[0].onSuccess(articles);
verify(mainView).removeWait();
verify(mainView).getNewsListSuccess(articles);
}
You can get rid of the ugly code by not using Mockito to mock RxJavaService, but rather roll you own hand-written mock, which would store the callback and provide it to the test.
However, I'd recommend a different approach.
Test with real RxJavaService and mocked NewsRestService
I'd say it makes more sense and gives a better test if we mocked only the NewsRestService:
#RunWith(MockitoJUnitRunner.class)
public class NewsList2Test {
private NewsPresenter newsPresenter;
#Mock
private MainView mainView;
#Mock
private NewsRestService newsRestService;
#Before
public void setUp() {
newsPresenter = new NewsPresenter(new RxjavaService(newsRestService), mainView);
}
#Test
public void show_success_in_view_when_there_are_articles() {
when(newsRestService.getNewsBySearch(eq("some url"), anyString(), anyInt()))
.thenReturn(Observable.just(new Articles()));
newsPresenter.getNewsList("some url");
verify(mainView).removeWait();
verify(mainView).getNewsListSuccess(any(Articles.class));
}
}

JUnit Tests for Liferay MVCPortlet using PowerMock

Im trying to make JUnit Test using PowerMock, but I have one problem. Here is my code:
public class MyGreeting extends MVCPortlet {
public static final String GREETING="greeting";
private static final String DEFAULT_GREETING="MY DEFAULT GREETING MESSAGE";
private static final Log _log = LogFactoryUtil.getLog(MyGreeting.class.getName());
#Override
public void render(RenderRequest req,RenderResponse res)
throws IOException, PortletException {
PortletPreferences prefs = req.getPreferences();
req.setAttribute(GREETING, prefs.getValue(GREETING, DEFAULT_GREETING));
super.render(req,res);
}
And I need to make JUnit test. I created another test package, new MyGreetingTest.java file, and come up to this code:
public class MyGreetingTest extends Mockito{
#BeforeClass
public static void setUpBeforeClass() throws Exception {
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
}
private MyGreeting portlet;
#Before
public void setUp() throws Exception {
portlet = new MyGreeting();
}
#After
public void tearDown() throws Exception {
}
#Mock
public RenderRequest request = mock(RenderRequest.class);
#Mock
PortletPreferences preferences = mock(PortletPreferences.class);
#Test
public final void renderTest() throws IOException, PortletException {
when(request.getPreferences()).thenReturn(preferences);
when(preferences.getValue(MyGreeting.GREETING, null)).thenReturn(value);
portlet.render(request, null);
String result = request.getAttribute(MyGreeting.GREETING).toString();
assertEquals(result, value);
}
But I have NullPointerException, because we can't apply getAttribute method to mock-request. Could you please tell me how to solve this problem? How can I test method with getAttribute method using Mockito?
I think you need to mock your method
Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00); // Mock implementation
when(stock.getValue()).thenCallRealMethod(); // Real implementation

Testing Katharsis JsonApi with MockMvc and Mockito

I would like to test the behaviour configured by my Katharsis ResourceRepository (katharsis-spring 2.1.7):
import io.katharsis.queryParams.QueryParams;
import io.katharsis.repository.ResourceRepository;
import org.springframework.stereotype.Component;
#Component
public class UserResourceRepository implements ResourceRepository<UserDTO, String> {
#Autowired
private UserRepository databaseRepository;
#Override
public UserDTO findOne(String email, QueryParams queryParams) {
return null;
}
#Override
public Iterable<UserDTO> findAll(QueryParams queryParams) {
return null;
}
#Override
public Iterable<UserDTO> findAll(Iterable<String> iterable, QueryParams queryParams) {
return null;
}
#Override
public void delete(String email) {
}
#Override
public UserDTO save(UserDTO s) {
return null;
}
}
I would like to test it in a similar way as I do it with normal, Spring Controllers, using Mockito to mock database repository and using MockMvc:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.Optional;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(MockitoJUnitRunner.class)
public class UserJsonApiTest {
#InjectMocks
private UserResourceRepository resourceRepository;
#Mock
private UserRepository databaseRepository;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(resourceRepository).build();
}
#Test
public void first() throws Exception {
Optional<UserEntity> user = Optional.of(new UserEntity().
id(1).
email("test#test").
firstName("test first name").
lastName("test last name").
pass("test pass"));
when(
databaseRepository.
findOneByEmail(user.get().getEmail())).
thenReturn(user);
mockMvc.perform(
get("/users/" + user.get().email())).
andExpect(status().isOk())
;
}
}
Obviously, this code doesn't work because my Katharsis UserResourceRepository is not really a Controller. So far I have learned (from logs) that it is actually using some filters mappings and class named io.katharsis.spring.KatharsisFilterV2.
Is there any way to use MockMvc for such test? If not - is there any other way I could test it without starting the whole server (with mocking)?
You could use an embedded Server - like UndertowJaxrsServer - and inject the KatharsisFeature:
Create a class (MyApp) which extends Application public static class MyApp extends Application { and deploy it to the embedded server server.deploy(MyApp.class);
in this Class, overwrite getClasses and add a second class (KatharsisFeatureTest) which implements Feature KatharsisFeatureTest implements Feature
in KatharsisFeatureTest you can then register a KatharsisFeature and there you can overwrite JsonServiceLocator and inject the mock.
Sound a little bit complicated, but works like charm :)
Have a look at my implementation.
.
#RunWith(MockitoJUnitRunner.class)
public class EndpointResourceTest {
#Mock
private EndpointService endpointService;
#InjectMocks
private final static EndpointResourceV1 endpointRessource = new EndpointResourceV1();
private static UndertowJaxrsServer server;
#BeforeClass
public static void beforeClass() throws Exception {
server = new UndertowJaxrsServer();
server.deploy(MyApp.class);
server.start();
}
#Test
public void testGetEndpoint() throws URISyntaxException {
Mockito.when(endpointService.getEndpoint("SUBMIT")).thenReturn(new EndpointDTO("SUBMIT", "a", "b"));
Client client = ClientBuilder.newClient();
Response response = client.target(TestPortProvider.generateURL("/api/endpoints/SUBMIT"))
.request(JsonApiMediaType.APPLICATION_JSON_API)
.get();
Assert.assertEquals(200, response.getStatus());
String json = response.readEntity(String.class);
Assert.assertTrue(json.contains("SUBMIT"));
Assert.assertTrue(json.contains("a"));
Assert.assertTrue(json.contains("b"));
Mockito.verify(endpointService, Mockito.times(1)).getEndpoint("SUBMIT");
}
#AfterClass
public static void afterClass() throws Exception {
server.stop();
}
#ApplicationPath("/api")
public static class MyApp extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<Class<?>>();
classes.add(KatharsisFeatureTest.class);
return classes;
}
}
public static class KatharsisFeatureTest implements Feature {
#Override
public boolean configure(FeatureContext featureContext) {
featureContext
.property(KatharsisProperties.RESOURCE_SEARCH_PACKAGE, "...")
.register(new io.katharsis.rs.KatharsisFeature(
new ObjectMapper(), new QueryParamsBuilder(new DefaultQueryParamsParser()), new SampleJsonServiceLocator() {
#Override
public <T> T getInstance(Class<T> clazz) {
try {
if (clazz.equals(EndpointResourceV1.class)) {
return (T) endpointRessource;
}
return clazz.newInstance();
}
catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}));
return true;
}
}
}

How to mock event in HttpModule

I have a simple Http module:
public class CustomLoggingModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
public void BeginRequest(object sender, EventArgs eventArgs)
{
//some code
}
public void EndRequest(object sender, EventArgs eventArgs)
{
//some
}
public void Dispose()
{
}
}
How can I unit test this? Especially how is it possible to mock events? Can anyone give some simple example?
Not sure why you have decided to hardwire the dependencies as new LogService() and new HttpContextWrapper(HttpContext.Current) within the CustomLoggingModule. If want to test whether LogInfo() method is called or not, it becomes lot easier if you can externalize these dependencies so you can inject stubbed/mocked version etc.
Also your question does not state that you are using an IOC container. You can register the HttpModule with the container and provide external dependencies at runtime. Your question also does not state that using an isoloation/mock object framework.
Therefore I will provide you with a solution that you can verify whether LogInfo method is called, using hand written stubs and mocks.
To achieve this, we need to refactor CustomLoggingModule a bit, so it becomes more testable.
System Under Test (SUT)
public class CustomLoggingModule : IHttpModule
{
public ILogService LogService { get; set; }
public Func<ILoggingHttpContextWrapper> LogginHttpContextWrapperDelegate { get; set; }
public void Init(HttpApplication context) {
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
public CustomLoggingModule() {
LogginHttpContextWrapperDelegate = () => new LoggingHttpContextWrapper();
}
public void BeginRequest(object sender, EventArgs eventArgs) {
LogService.LogInfo(LogginHttpContextWrapperDelegate().HttpContextWrapper);
}
public void EndRequest(object sender, EventArgs eventArgs) {
//some
}
public void Dispose(){ }
}
As you see above, I have introduced 2 additional properties - ILogService so I can provide a Mocked verion and a delegate Func which allows me to stub the
new HttpContextWrapper(HttpContext.Current);
public interface ILoggingHttpContextWrapper {
HttpContextWrapper HttpContextWrapper { get; }
}
public class LoggingHttpContextWrapper : ILoggingHttpContextWrapper
{
public LoggingHttpContextWrapper() {
HttpContextWrapper = new HttpContextWrapper(HttpContext.Current);
}
public HttpContextWrapper HttpContextWrapper { get; private set; }
}
And then your real ILogService
public interface ILogService {
void LogInfo(HttpContextWrapper httpContextWrapper);
}
public class LogService : ILogService {
public void LogInfo(HttpContextWrapper httpContextWrapper)
{
//real logger implementation
}
}
Unit Test :
You would create a MockLoggerService, so you can verify the interaction i,e whether the LogInfo() method was called, etc. You also need a stubbed LoggingHttpContextWrapper to provide the fake HttpContextWrapper to the SUT (System Under Test)/ CustomLoggingModule.
public class StubLoggingHttpContextWrapper : ILoggingHttpContextWrapper
{
public StubLoggingHttpContextWrapper(){}
public HttpContextWrapper HttpContextWrapper { get; private set; }
}
public class MockLoggerService : ILogService
{
public bool LogInfoMethodIsCalled = false;
public void LogInfo(HttpContextWrapper httpContextWrapper) {
LogInfoMethodIsCalled = true;
}
}
MockLoggerService is very important. It is not the real logger service, but it is the mocked version. When we do public class MockLoggerService : ILogService this means that we are providing another layer of indirection to the logger service so we can verify the interaction of the behaviour.
You also notice that I have provided a boolean variable to verify whether the LogInfo method is called or not. This allows me to call this method from the SUT, and verify whether the method being called or not.
Now Your Unit Test can be implemented as below.
[TestMethod]
public void CustomLoggingModule_BeginRequest_VerifyLogInfoMethodIsCalled()
{
var sut = new CustomLoggingModule();
var loggerServiceMock = new MockLoggerService();
var loggingHttpContextWrapperStub = new StubLoggingHttpContextWrapper();
sut.LogService = loggerServiceMock;
sut.LogginHttpContextWrapperDelegate = () => loggingHttpContextWrapperStub;
sut.BeginRequest(new object(), new EventArgs());
Assert.IsTrue(loggerServiceMock.LogInfoMethodIsCalled);
}
I had the same issue with my custom http module and decided I won't give up that easily and will do all I can to trigger the BeginRequest event in unit test. I had to actually read through the source code of HttpApplication class and use reflection to invoke the method.
[TestMethod]
public void EventTriggered_DoesNotError()
{
using (var application = new HttpApplication())
{
var module = new CustomLoggingModule();
module.Init(application);
FireHttpApplicationEvent(application, "EventBeginRequest", this, EventArgs.Empty);
}
}
private static void FireHttpApplicationEvent(object onMe, string invokeMe, params object[] args)
{
var objectType = onMe.GetType();
object eventIndex = (object)objectType.GetField(invokeMe, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);
EventHandlerList events = (EventHandlerList)objectType.GetField("_events", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(onMe);
EventHandler handler = (EventHandler)events[eventIndex];
Delegate[] delegates = handler.GetInvocationList();
foreach (Delegate dlg in delegates)
{
dlg.Method.Invoke(dlg.Target, args);
}
}

NUnit Conditional Teardown?

Is there a way to do a conditional TearDown in NUnit?
I have a TestFixture which has a need to run cleanup code for just a few tests, and I don't really want to:
Run the TearDown method on every test
Create a private helper method and call it from the tests requiring cleanup if I can avoid it
There isn't unfortunately.
Can you not do the cleanup in the [TestFixtureTearDown] instead, so once all the tests have finished? I guess that depends on whether the cleanup has to be done before the next test runs.
Alternatively, put those tests that require a cleanup in another class/TextFixture together, away from the other tests. Then you can use a TearDown in there which doesn't need to be conditional.
Edit:
One thing I've just thought of, which could be done to achieve the aim though probably isn't actually worth it for this particular need, is that you can extend NUnit - create your own custom attributes which you could handle however you wanted. This is mentioned here. Like I say, I don't think really you should go down that route for this, but is useful to know none-the-less
You can have the main TearDown in a base class:
[TearDown]
public virtual void TearDown()
{
// Tear down things here
}
and then override it in the class where you have the tests that should not run the tear down code:
[TearDown]
public override void TearDown()
{
// By not calling base.TearDown() here you avoid tearing down
}
Extend all you classes with test from BaseTest
public class BaseTest
{
[SetUp]
public void BeforeTest()
{
GetService<NUnitHooksController>().ExecuteBeforeTestHooks(this);
}
[TearDown]
public void AfterTest()
{
GetService<NUnitHooksController>().ExecuteAfterTestHooks(this);
}
}
Use AfterTest and BeforeTest hooks. Works both with and without category.
public class ExampleTest : BaseTest
{
[Test, Category("asdasd")]
public void Test01()
{
...
}
[AfterTest("asdasd")]
public void ExampleHook()
{
...
}
}
public class NUnitHooksController
{
private readonly ILogger _log;
public NUnitHooksController(ILogger log)
{
_log = log;
}
public void ExecuteBeforeTestHooks(object testClass)
{
ExecuteHooks(testClass, typeof(BeforeTestAttribute));
}
public void ExecuteAfterTestHooks(object testClass)
{
ExecuteHooks(testClass, typeof(AfterTestAttribute));
}
private MethodInfo[] GetHookMethods(object currentTestClass, Type attributeType)
{
return currentTestClass
.GetType()
.GetMethods()
.Where(m => m.GetCustomAttributes(attributeType, false).Length > 0)
.ToArray();
}
private void ExecuteHooks(object testClass, Type requiredAttributeType)
{
var hooks = GetHookMethods(testClass, requiredAttributeType);
var testCategories = GetTestCategories();
foreach (var hook in hooks)
{
var allAttributes = hook.GetCustomAttributes(requiredAttributeType, true);
foreach (var attribute in allAttributes)
{
if (!attribute.GetType().IsEquivalentTo(requiredAttributeType))
{
continue;
}
var hookCategories = GetCategoriesFromAttribute(attribute);
// if we do not have specific category on hook
// or we have at least one same category on hook and test
if (!hookCategories.Any() || hookCategories.Intersect(testCategories).Any())
{
ExecuteHookMethod(testClass, hook);
}
}
}
}
private object[] GetTestCategories()
{
return TestContext.CurrentContext.Test.Properties["Category"].ToArray();
}
private void ExecuteHookMethod(object testClass, MethodInfo method)
{
var hookName = method.Name;
_log.Information($"Executing - '{hookName}' hook");
try
{
method.Invoke(testClass, Array.Empty<object>());
}
catch (Exception e)
{
_log.Error($"Executing of - '{hookName}' hook failed - {e}");
}
}
private string[] GetCategoriesFromAttribute(object attribute)
{
if (attribute is BeforeTestAttribute beforeTestAttribute)
{
return beforeTestAttribute.Categories;
}
if (attribute is AfterTestAttribute afterTestAttribute)
{
return afterTestAttribute.Categories;
}
throw new ArgumentException($"{attribute.GetType().FullName} - does not have categories");
}
}
I have solved this using the name of the test:
namespace TestProject
{
public class TestClass
{
// Test without TearDown
[Test]
public void Test1()
{
Assert.Pass("Test1 passed");
}
// Test with TearDown
[Test]
public void Test2()
{
Assert.Pass("Test2 passed");
}
[TearDown]
public void TearDown()
{
// Execute only after Test2
if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))
{
// Execute Test2 TearDown...
}
}
}
}
Or if you want to use the full name of Test2 (TestProject.TestClass.Test2) you can replace the line
if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))
by
if (TestContext.CurrentContext.Test.FullName.Equals(typeof(TestClass).FullName + "." nameof(this.Test2)))