Unity3D TestRunner hangs when using NSubstitute to mock a Monobehavior class - unit-testing

I have stripped my project down to the bare minimum to try to figure out this instance where Unity TestRunner hangs. I have two Monobehaviors classes, GameController and TrialController. I followed Jason Wiemann's excellent testing videos Everything you need to know about Testing in Unity3D and Testing Against Monobehaviors Using Mocks to create an interface class, ITrialController, so that I can mock it for testing GameController. The TestRunner hangs when I try to run TestWithGetNextSubstitute(). What am I doing wrong here?
============== GameCtrlTest.cs ===========================
using System.Collections;
using System.Collections.Generic;
using NSubstitute;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Tests
{
public class GameCtrlTest
{
GameController gameCtrl;
[SetUp]
public void TestSetup()
{
GameObject gameObject = new GameObject("GameController");
gameCtrl = gameObject.AddComponent<GameController>();
gameCtrl.trialCtrl = Substitute.For<ITrialController>();
Assert.IsNotNull(gameCtrl.trialCtrl);
Debug.Log("Test Setup() done");
}
[UnityTest]
public IEnumerator TestWithGetNextSubstitute()
{
gameCtrl.trialCtrl.GetNextTrial().Returns( "test-1", "test-2", null);
yield return null;
}
}
}
================== TrialController.cs ================================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
public class TrialController : MonoBehaviour, ITrialController
{
int trialsLeft = 2;
public string GetNextTrial()
{
if (trialsLeft == 0)
return null;
else
{
string returnString = "trial--" + trialsLeft;
trialsLeft--;
return (returnString);
}
}
}
==================== ITrialController.cs =================
public interface ITrialController
{
string GetNextTrial();
}
==================== GameController.cs ==================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// give Test Runner access to internal variables and methods
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Tests")]
public class GameController : MonoBehaviour
{
public ITrialController trialCtrl { get; set; }
//=================== MonoBehavior interface =======================
void Awake()
{
Debug.Log("gameController Awake()");
}
// Start is called before the first frame update
void Start()
{
Debug.Log("gameController Start()");
if (trialCtrl != null)
Debug.Log("trialCtrl already set");
else
{
Debug.Log("calling FindObjectOfType(typeof(TrialController)");
trialCtrl = (ITrialController)FindObjectOfType(typeof(TrialController));
Debug.Assert(trialCtrl != null, "Problem finding TrialController");
}
}
// Update is called once per frame
static bool levelInitialized = false;
void Update()
{
if (!levelInitialized)
{
Debug.Log("gameController first Update()");
levelInitialized = true;
}
string trialName = trialCtrl.GetNextTrial();
while (trialName != null)
{
Debug.Log($"GameController got {trialName} from GetNextTrial");
trialName = trialCtrl.GetNextTrial();
}
}
}

The fix was to move the GetNextTrial().Returns() call from the [UnityTest] to the [Setup]. This initially seems very limiting, but there is probably a way to move the parameter list ("test-1", "test-2", null) into a variable so I can change it for different tests.
=============== GameCtrlTest.cs ===================================
using System.Collections;
using System.Collections.Generic;
using NSubstitute;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Tests
{
public class GameCtrlTest
{
GameController gameCtrl;
[SetUp]
public void TestSetup()
{
GameObject gameObject = new GameObject("GameController");
gameCtrl = gameObject.AddComponent<GameController>();
gameCtrl.trialCtrl = Substitute.For<ITrialController>();
Assert.IsNotNull(gameCtrl.trialCtrl);
gameCtrl.trialCtrl.GetNextTrial().Returns("test-1", "test-2", null);
Debug.Log("Test Setup() done");
}
[UnityTest]
public IEnumerator TestWithGetNextSubstitute()
{
yield return null;
}
}
}

Related

How do i keep an integer after the code ran once?

I just started out on C# and I wanted to make a voice recognition program that counts your words and for now, warns you after a said number is passed. I have asked someone on discord and they said I should use a dictionary in order to do that but I don't know how to implement that into this so I made it with if statements, here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Speech.Recognition;
using System.Speech.Synthesis;
namespace VoiceRecognition
{
public partial class SpeechRecognition : Form
{
SpeechRecognitionEngine recEngine = new SpeechRecognitionEngine();
SpeechSynthesizer synthesizer = new SpeechSynthesizer();
public SpeechRecognition()
{
InitializeComponent();
}
private void On_Click(object sender, EventArgs e)
{
recEngine.RecognizeAsync(RecognizeMode.Multiple);
On.Enabled = true;
}
private void SpeechRecognition_Load(object sender, EventArgs e)
{
Choices commands = new Choices();
commands.Add(new string[] { "bad" });
GrammarBuilder gBuilder = new GrammarBuilder();
gBuilder.Append(commands);
Grammar grammar = new Grammar(gBuilder);
recEngine.LoadGrammarAsync(grammar);
recEngine.SetInputToDefaultAudioDevice();
recEngine.SpeechRecognized += recEngine_SpeechRecognized;
}
void recEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
switch (e.Result.Text)
{
case "bad":
int badwordcount = 0;
if (badwordcount == 0)
{
synthesizer.SpeakAsync("i will suggest you to stop.");
badwordcount +=1;
}
else if (badwordcount == 1)
{
synthesizer.SpeakAsync("You are getting quite annoying.");
badwordcount += 1;
}
if(badwordcount == 2)
{
synthesizer.SpeakAsync("Thats it, you are done.");
}
break;
}
}
private void Off_Click(object sender, EventArgs e)
{
recEngine.RecognizeAsyncStop();
Off.Enabled = false;
}
}
}
The problem lies in the integer from line 51 (badwordcount), no matter how much I repeat the word bad, it just does not respond with the "You are getting quite annoying." line. Any ideas on how do I save the integer so it knows what to say after it hears it a second time? (sorry for the long text, I don't know how to express my words very well).

How to stop MsTest tests execution after *n* failed tests

I want to run unit tests via MS Test (from windows console) in a way that I can stop/kill the test execution whenever the failed tests count exceeds certain threshold value.
For my use case there is no point to keep running tests when certain percentage of the tests already failed.
I can only think in creating a new console app to wrap the mstest.exe execution, so I can parse the standard output in real-time,
and eventually kill the process, for example:
var pi = new ProcessStartInfo()
{
FileName = MS_TEST,
UseShellExecute = false,
RedirectStandardOutput = true,
Arguments = MS_TEST_ARGS
};
int passed = 0; int failed = 0;
using (var process = Process.Start(pi))
{
while (!process.StandardOutput.EndOfStream)
{
string line = process.StandardOutput.ReadLine();
if (line.Contains("Passed"))
passed++;
if (line.Contains("Failed"))
failed++;
if (failed >= THRESHOLD)
{
process.Kill();
break;
}
}
}
Can anyone suggest a better way for doing this? I don't think this is natively supported by MsTest.
PowerShell seems to be an option, but the stdout redirect is not trivial.
Update
As a note, I cannot modify the test code, I need this to be done without modifying the tests code in any way.
Create a BaseTestClass which contains a method responsible for killing the process that runs the tests.
using System.Diagnostics;
namespace UnitTestProject1
{
public class BaseTestClass
{
private readonly int _threshold = 1;
private static int _failedTests;
protected void IncrementFailedTests()
{
if (++_failedTests >= _threshold)
Process.GetCurrentProcess().Kill();
}
}
}
Your must inherit all your test classes from BaseTestClass and use the [TestCleanup] attribute. The TestCleanup() method is evaluated when a test defined in the DemoTests class has finished running. Is in that method where we evaluate the output of the test that has just finished. If it failed, we kill the process responsible for running the tests.
In the following example we have defined three tests. The second test, Test_Substracting_Operation(), is intended to fail intentionally, so the third test will never be run.
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject1
{
[TestClass]
public class DemoTests : BaseTestClass
{
public TestContext TestContext { get; set; }
[TestCleanup]
public void TestCleanup()
{
if (TestContext.CurrentTestOutcome == UnitTestOutcome.Failed)
{
IncrementFailedTests();
}
}
[TestMethod]
public void Test_Adding_Operation()
{
// Arrange
int x = 1;
int y = 2;
// Act
int result = x + y;
// Assert
Assert.AreEqual(3, result);
}
[TestMethod]
public void Test_Substracting_Operation()
{
// Arrange
int x = 1;
int y = 2;
// Act
int result = x - y;
// Assert
Assert.AreEqual(100, result);
}
[TestMethod]
public void Test_Multiplication_Operation()
{
// Arrange
int x = 1;
int y = 2;
// Act
int result = x * y;
// Assert
Assert.AreEqual(2, result);
}
}
}

Unit test just returns 1 result of 10 cases from loop

i am trying to get all 10 results of 10 cases from For loop. but when i run, it just returns for me the first result of the first time. any help for this condition, this is my whole code, it includes
2 files, i have tried many times to fix it.
//file BankAccount.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bank //just want to demo this thing, it hasn't completed
{
namespace BankAccountNS
{
public class BankAccount
{
private double m_balance;
public BankAccount(double balance)
{
m_balance = balance;
}
public bool getMoney(double amount) //funtion get money from account
{
if (amount > m_balance || amount < 0) //check money
{
return false;
}
return true;
}
}
}
}
//file BankAccountTests.cs
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Bank.BankAccountNS;
namespace BankTest
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestEveryDebit(BankAccount Ba) //test every case from TestAll
{
Assert.IsTrue(Ba.getMoney(24000));
}
[TestMethod]
public void TestAll() //create all cases
{
for(int i = 0; i < 10; i++)
{
BankAccount Ba = new BankAccount(23996 + i);
TestEveryDebit(Ba);
}
}
}
}
I'm not really clear on what your (attempted) loop asserts would be accomplishing, but the method getMoney seemingly has 2 (or 3) useful unit tests:
Is the amount greater than the balance I have? - return false
Is my account balance less than zero - return false
Is my amount less than or equal too my balance? - return true
In your current setup (if it were to work) you're simply testing getMoney is returning true for amounts even greater than the balance - this is incorrect and does not adhere to the logic you have coded too.
I see your unit tests looking like:
private double _balance = 50;
private BankAccount _unitTestObject;
[TestMethod]
public void getMoney_returnsFalseWithInsufficientFunts() //create all cases
{
_unitTestObject = new BankAccount(_balance );
var results = _unitTestObject.getMoney(_balance+1);
Assert.IsFalse(results);
}
[TestMethod]
public void getMoney_returnsFalseWhenAccountHasLessThanZero() //create all cases
{
_unitTestObject = new BankAccount(-1);
var results = _unitTestObject.getMoney(1);
Assert.IsFalse(results);
}
[TestMethod]
public void getMoney_returnsTrueWhenAccountSufficientBalance() //create all cases
{
_unitTestObject = new BankAccount(_balance);
var results = _unitTestObject.getMoney(_balance);
Assert.IsTrue(results);
}
As I stated in comments, MSTest can't do parameterized tests, and what it looks like you're attempting to do (assert specific logic 10 times) could be done like this:
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestAll() //create all cases
{
for(int i = 0; i < 10; i++)
{
BankAccount Ba = new BankAccount(23996 + i);
TestEveryDebit(Ba);
}
}
private void TestEveryDebit(BankAccount Ba) //test every case from TestAll
{
Assert.IsTrue(Ba.getMoney(24000));
}
}
But the test TestAll will always fail, because at some point in your loop, you're going to be trying to take out more amount than you have balance.
When Asserting based on a loop, the "success or failure" of the test is based on the whole, not each individual assert. So even though a few runs of your loop will "pass", the test will fail as a whole.

Mocking void methods using Mockito

I cannot seem to mock void methods on Mockito. It gives a unfinished stubbing detected here error. Here is my classfile.
package com.twu.biblioteca;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.InputMismatchException;
import java.util.Scanner;
public class BibliotecaApp {
public static class IntegerAsker {
private final Scanner scanner;
private final PrintStream out;
public IntegerAsker(InputStream in, PrintStream out) {
scanner = new Scanner(in);
this.out = out;
}
public int ask(String message) {
out.print(message);
return scanner.nextInt();
}
}
public static int numberOfBooks = 0;
public static class book{
int serialNo;
String name;
String author;
int publication;
int checkoutstatus;
book(){
serialNo = -1;
name = null;
author = null;
publication = -1;
checkoutstatus = -1;
}
book(int serialNo,String name, String author, int publication){
this.serialNo = serialNo;
this.name = name;
this.author = author;
this.publication = publication;
this.checkoutstatus=checkoutstatus = 1;
}
}
public static int getBoundIntegerFromUser(IntegerAsker asker,String message,int lowerBound,int upperBound) {
int input;
try
{
input = asker.ask(message);
while(input>upperBound || input<lowerBound)
input = asker.ask("Select a valid option! ");
return input;
}
catch(InputMismatchException exception)
{
System.out.print("You have selected an invalid option! ");
}
return -1;
}
public static book[] booksList = new book[20];
public static String welcome(){
IntegerAsker asker = new IntegerAsker(System.in,System.out);
return "**** Welcome Customer! We are glad to have you at Biblioteca! ****";
}
public static void addBooks(){
book newBook1 = new book(1,"Head First Java","Bert Bates",2014);
booksList[1] = newBook1;
numberOfBooks += 1;
book newBook2 = new book(2,"1000 IT Quizzes","Dheeraj Malhotra",2009);
booksList[2] = newBook2;
numberOfBooks += 1;
book newBook3 = new book(3,"100 Shell Programs in Unix","Shivani Jain",2009);
booksList[3] = newBook3;
numberOfBooks += 1;
}
public static void mainMenu(IntegerAsker asker){
System.out.println("1 " + "List Books");
System.out.println("2" + " Checkout a Book");
System.out.println("3 " + "Quit");
int n = getBoundIntegerFromUser(asker,"Enter your choice. ",1,3);
mainMenuaction(n,asker);
}
public static void mainMenuaction(int n,IntegerAsker asker){
if(n==1){
showBooks();
mainMenu(asker);
}
else if(n==2){
checkout(asker);
}
else if(n==3){
return;
}
}
public static void showBooks(){
for(int i=1;i<=numberOfBooks;i++){
if(booksList[i].checkoutstatus!=0)
System.out.println(booksList[i].serialNo + ".\t" + booksList[i].name + "\t" + booksList[i].author + "\t" + booksList[i].publication);
}
}
public static void checkout(IntegerAsker asker){
int Input = asker.ask("Enter the serial numebr of the book that you want to checkout");
if(booksList[Input]!=null){
if(booksList[Input].checkoutstatus!=0){
booksList[Input].checkoutstatus=0;
System.out.println("Thank you! Enjoy the book");
}
else{
System.out.println("That book is not available.");
}
}
else{
System.out.println("That book is not available.");
}
mainMenu(asker);
}
public static void main(String[] args) {
System.out.println(welcome());
addBooks();
IntegerAsker asker = new IntegerAsker(System.in,System.out);
mainMenu(asker);
}
}
And here goes my test file -
package com.twu.biblioteca;
import org.mockito.Mockito;
import org.mockito.Mockito.*;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class ExampleTest {
BibliotecaApp test = Mockito.mock(BibliotecaApp.class);
#Test
public void welcometest() {
assertEquals("**** Welcome Customer! We are glad to have you at Biblioteca! ****",test.welcome());
}
#Test
public void addBooksTest(){
test.addBooks();
assertEquals("Head First Java",test.booksList[1].name);
assertEquals("Dheeraj Malhotra",test.booksList[2].author);
assertEquals(2009,test.booksList[3].publication);
}
#Test
public void getBoundIntegerFromUserTest(){
BibliotecaApp.IntegerAsker asker = mock(BibliotecaApp.IntegerAsker.class);
when(asker.ask("Enter your choice. ")).thenReturn(99);
when(asker.ask("Select a valid option! ")).thenReturn(1);
BibliotecaApp.getBoundIntegerFromUser(asker,"Enter your choice. ",1,2);
verify(asker).ask("Select a valid option! ");
}
#Test
public void mainMenuTest(){
BibliotecaApp.IntegerAsker asker = mock(BibliotecaApp.IntegerAsker.class);
when(asker.ask("Enter your choice. ")).thenReturn(3);
test.mainMenu(asker);
verify(test).mainMenuaction(1,asker);
}
#Test
public void checkoutTest(){
BibliotecaApp.IntegerAsker asker = mock(BibliotecaApp.IntegerAsker.class);
BibliotecaApp test = new BibliotecaApp();
BibliotecaApp mock = spy(test);
when(asker.ask("Enter the serial numebr of the book that you want to checkout")).thenReturn(2);
Mockito.doNothing().when(mock).mainMenu(asker);
test.addBooks();
test.checkout(asker);
assertEquals(0,test.booksList[2].checkoutstatus);
}
}
Can someone point out what I am doing wrong please ?
/* system */ public static void mainMenu(IntegerAsker asker){ ... }
/* test */ Mockito.doNothing().when(mock).mainMenu(asker);
Your problem isn't about mocking void methods, it's about mocking static methods, which Mockito can't do. Behind the scenes, Mockito is creating an override of your mocked/spied class (BibliotecaApp) to override each of the methods, but because static methods can't be overridden the same way, Mockito can't change mainMenu's behavior—even just to detect that you called it in the stubbing, which is why this shows up as "unfinished stubbing".
Remove the static modifier from mainMenu and you'll be over that hurdle.
Side note: You also spy on a class but keep the original around. This isn't a good idea in Mockito: A spy actually creates a copy of the object, so if you're relying on behavior that applies to the spy, you'll have to call the test methods on the spy. (This is part of the reason to avoid spies in your tests: using spies can blur the line between testing your system's behavior and testing Mockito's behavior.)
BibliotecaApp test = new BibliotecaApp();
BibliotecaApp mock = spy(test);
when(asker.ask("...")).thenReturn(2);
Mockito.doNothing().when(mock).mainMenu(asker);
test.addBooks(); // should be: mock.addBooks()
test.checkout(asker); // should be: mock.checkout(asker)

Creating a Windows Forms Control (C++)

trying to run this basic form control example on msdn.
At step 1 of the portion "To add a custom property to a control" we place the ClickAnywhere code in the public section of the class.
First error: "error C2144: syntax error : 'bool' should be preceded by ';'"
Is this syntax correct in C++? (see below)
(removing the ClickAnywhere portion of code, it compiles fine...)
#pragma once
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
namespace clickcounter
{
/// <summary>
/// Summary for clickcounterControl
/// </summary>
///
/// WARNING: If you change the name of this class, you will need to change the
/// 'Resource File Name' property for the managed resource compiler tool
/// associated with all .resx files this class depends on. Otherwise,
/// the designers will not be able to interact properly with localized
/// resources associated with this form.
public __gc class clickcounterControl : public System::Windows::Forms::UserControl
{
public:
//Problem code*****
property bool ClickAnywhere { //Is this syntax right in C++?
bool get() {
return (label1->Dock == DockStyle::Fill);
}
void set(bool val) {
if (val)
label1->Dock = DockStyle::Fill;
else
label1->Dock = DockStyle::None;
}
}
//End Problem code*****
clickcounterControl(void)
{
InitializeComponent();
}
protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::Label * label1;
private:
/// <summary>
/// Required designer variable.
/// </summary>
System::ComponentModel::Container* components;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
this->label1 = new System::Windows::Forms::Label();
this->SuspendLayout();
//
// label1
//
this->label1->BorderStyle = System::Windows::Forms::BorderStyle::FixedSingle;
this->label1->Location = System::Drawing::Point(32, 40);
this->label1->Name = S"label1";
this->label1->Size = System::Drawing::Size(30, 20);
this->label1->TabIndex = 0;
this->label1->Text = S"0";
this->label1->TextAlign = System::Drawing::ContentAlignment::MiddleCenter;
this->label1->Click += new System::EventHandler(this, label1_Click);
//
// clickcounterControl
//
this->Controls->Add(this->label1);
this->Name = S"clickcounterControl";
this->Size = System::Drawing::Size(100, 100);
this->ResumeLayout(false);
}
private: System::Void label1_Click(System::Object * sender, System::EventArgs * e)
{
int temp = System::Int32::Parse(label1->Text);
temp++;
label1->Text = temp.ToString();
}
};
}
Since you are using Visual Studio .Net 2003, you are using Managed C++, not C++/CLI. There is a significant difference in syntax. For a property, you must use the __property keyword, not the C++/CLI property keyword and its new style.
It should therefore be:
__property bool get_ClickAnywhere() {
return (label1->Dock == DockStyle::Fill);
}
__property void set_ClickAnywhere(bool value) {
if (value)
label1->Dock = DockStyle::Fill;
else
label1->Dock = DockStyle::None;
}
It looks like you are being tripped up by following a guide written for C++/CLI (Visual Studio 2005 and later) while still using Visual Studio 2003.