I have created a trigger that converts products(Lineitems) to assets when opportunity moves to closed won stage. It is working fine on Sandbox but the problem is code coverage. When I'm writing a test class the method is passed but the code coverage of trigger is still 0%. Any help would be appreciated.
#Trigger
trigger OpportunityTrigger on Opportunity (after insert,after update) {
OpportunityTriggerHandler.testhandler(trigger.new); }
#Class
public Class OpportunityTriggerHandler {
public static Boolean isFirstTime = true;
public static void testhandler(List <Opportunity> oplist)
{
list <OpportunityLineItem> OLI=new list<OpportunityLineItem>();
list<Asset> ast = new list<Asset>();
for(Opportunity o: oplist) {
if(o.isWon == true && o.HasOpportunityLineItem == true && o.AccountId !=null)
{
String opptyId = o.Id;
OLI = [Select UnitPrice, Quantity , PricebookEntry.Product2Id, PricebookEntry.Product2.Name, Description From OpportunityLineItem where OpportunityId = :opptyId ];
Asset a = new Asset();
for(OpportunityLineItem ol: OLI){
List<Asset> ast1 = [Select Product2Id From Asset where Opportunity__c = :opptyId and Product2Id = : ol.PricebookEntry.Product2Id];
if(ast1 .size()>0){
string prdId = string.valueOf(ast1[0].Product2Id );
string oliId = ol.PricebookEntry.Product2Id;
if(prdId != oliId ){
a = new Asset();
a.AccountId = o.AccountId;
a.Product2Id = ol.PricebookEntry.Product2Id;
a.Quantity = ol.Quantity;
a.Price = ol.UnitPrice;
a.PurchaseDate = o.CloseDate;
a.Status = 'Purchased';
a.Description = ol.Description;
a.Name = ol.PricebookEntry.Product2.Name;
a.Opportunity__c = o.Id;
ast.add(a);
}
}
else{
a = new Asset();
a.AccountId = o.AccountId;
a.Product2Id = ol.PricebookEntry.Product2Id;
a.Quantity = ol.Quantity;
a.Price = ol.UnitPrice;
a.PurchaseDate = o.CloseDate;
a.Status = 'Purchased';
a.Description = ol.Description;
a.Name = ol.PricebookEntry.Product2.Name;
a.Opportunity__c = o.Id;
ast.add(a);
}
}
} } update OLI; insert ast; } }
#testClass
#isTest
public class TestOpportunityTrigger{
Static testmethod void TestOpportunityTrigger(){
Test.startTest();
list <Opportunity> Opty =new list<Opportunity>();
OpportunityTriggerHandler.testhandler(Opty);
Opportunity o = new Opportunity();
OpportunityLineItem ol = new OpportunityLineItem();
Asset a = new Asset();
if(o.isWon == true && o.HasOpportunityLineItem == true && o.AccountId !=null)
{
a = new Asset();
a.AccountId = o.AccountId;
a.Product2Id = ol.PricebookEntry.Product2Id;
a.Quantity = ol.Quantity;
a.Price = ol.UnitPrice;
a.PurchaseDate = o.CloseDate;
a.Status = 'Purchased';
a.Description = ol.Description;
a.Name = ol.PricebookEntry.Product2.Name;
a.Opportunity__c = o.Id;
try{ insert a; }
catch(exception e)
{ system.assert(False); }
Test.stopTest();
}
else{}
}}
Related
Hello! I am unable to test the written apex class.. Could you help me with this task?
Through the component, we transfer the ID of the CSV file sent to the server to the apex class, after which our apex class creates stations if there were none before, and also updates the list of sensors based on the records of the CSV file (insert/ update).
Controller Class
public inherited sharing class lwnReadCsvFileController {
#AuraEnabled
public static list<Sensor__c> readCSVFile(Id idContentDocument){
list<Sensor__c> lstSensToInsert = new list<Sensor__c>();
if(idContentDocument != null) {
// getting File Data based on document id
ContentVersion objVersion = [SELECT Id, VersionData FROM ContentVersion WHERE ContentDocumentId =:idContentDocument];
// split the file data
list<String> lstCSVLines = objVersion.VersionData.toString().split('\n');
//creating Basic Stations records
List<Base_Station__c> createdBSRec = [SELECT Name, Id From Base_Station__c];
List<Base_Station__c> newBSRec = new List<Base_Station__c>();
Set<String> uniqueSet = new Set<String>();
for ( Integer i = 1; i < lstCSVLines.size(); i++ ) {
integer coincidences = 0;
list<String> csvRowData = lstCSVLines[i].split(',');
for ( Base_Station__c rec : createdBSRec ) {
if (csvRowData[0] == rec.Name) {
coincidences++;
}
}
if ( coincidences == 0 ) {
uniqueSet.add(csvRowData[0]);
}
}
List<String> uniquelist = new List<String>(uniqueSet);
for (integer i = 0; i < uniquelist.size() ; i++) {
Base_Station__c newBS = new Base_Station__c(Name = uniquelist[i]);
NewBSRec.add(newBS);
}
upsert newBSRec;
//creating Sensor records
for(Integer i = 1; i < lstCSVLines.size(); i++){
Sensor__c objRec = new Sensor__c();
list<String> csvRowData = lstCSVLines[i].split(',');
string tempBase = csvRowData[0];
objRec.Name = csvRowData[1];
objRec.Base_Station__c = [SELECT Id From Base_Station__c Where Name = :tempBase ].id;
objRec.Sensor_ID__c = integer.valueof(csvRowData[1]);
objRec.Status__c = csvRowData[2];
objRec.Sensor_Model__c = csvRowData[3];
lstSensToInsert.add(objRec);
}
if(!lstSensToInsert.isEmpty()) {
List<Sensor__c> createdSenRec = [SELECT Name, Sensor_ID__c From Sensor__c];
for (Sensor__c sens : lstSensToInsert ) {
integer coincidences = 0;
for (Sensor__c rec : createdSenRec ) {
if (sens.Sensor_ID__c == rec.Sensor_ID__c) {
sens.id = rec.id;
update sens;
coincidences++;
}
}
if ( coincidences == 0 ) {
insert sens;
}
}
}
}
return lstSensToInsert;
}
}
Test Class 100% coverage
#isTest
public class IwnReadCsvFileControllerTest {
public static String str = 'BASE STATION,SENSOR ID,STATUS,SENSOR MODEL \n' +
'Leeds,1,Enabled ,R8 \n' +
'Glasgow Central,2,Enabled,R8';
#isTest
public static void testReadCSVFile(){
Base_Station__c newBS = new Base_Station__c(Name = 'Leeds');
upsert newBS;
Sensor__c newSensor = new Sensor__c (Name = '1', Sensor_Id__c = 1, Status__c = 'Enabled', Base_Station__c = newBs.id);
insert newSensor;
ContentVersion contentVersionInsert = new ContentVersion(
Title = 'Test',
PathOnClient = 'Test.csv',
VersionData = Blob.valueOf(str),
IsMajorVersion = true
);
insert contentVersionInsert;
Id getId = [Select ContentDocumentId From ContentVersion Where Id =:contentVersionInsert.id and isLatest=true].ContentDocumentId;
List<Sensor__c> result = lwnReadCsvFileController.readCSVFile(getId);
}
}
Your unit test class is passing a ContentVersion Id:
List<Sensor__c> result = lwnReadCsvFileController.readCSVFile(contentVersionInsert.Id);
but your class is treating this Id as a ContentDocument Id:
public static list<Sensor__c> readCSVFile(Id idContentDocument){
if(idContentDocument != null) {
ContentVersion objVersion = [
SELECT Id, VersionData
FROM ContentVersion
WHERE ContentDocumentId =:idContentDocument
];
Your test class needs to query for the ContentDocumentId of the newly-inserted ContentVersion and pass that Id into your class under test.
I looked into google people API on their official documentation page
https://developers.google.com/people/quickstart/java#step_4_run_the_sample
in spite of following all necessary steps API does not work.
I came across below example on GitHub which is not working as well. https://github.com/Suleiman19/People-API-App
also, I check google example
Credential credential = new AuthorizationCodeInstalledApp(
flow, new LocalServerReceiver()).authorize("user");
lang.NoClassDefFoundError: Failed resolution of: Ljava/awt/Desktop;
Below code is working. able to access people API
private static class GetUserInfoTask extends AsyncTask<String, Void, Void> {
private final Context context;
private ISignIn iSignIn;
private HttpTransport httpTransport = new NetHttpTransport();
private JacksonFactory jsonFactory = new JacksonFactory();
private String personEmail;
private String personLastName;
private String personFirstName;
private String image;
private String genderString;
private String aboutMeLocal;
private String phoneLocal;
private long birthdayLocal;
public GetUserInfoTask(Context context, ISignIn iSignIn) {
this.context = context;
this.iSignIn = iSignIn;
}
#Override
protected Void doInBackground(String... params) {
personEmail = params[0];
personLastName = params[1];
personFirstName = params[2];
image = params[3];
Person userProfile = null;
Collection<String> scopes = new ArrayList<>();
scopes.add(Scopes.PROFILE);
GoogleAccountCredential mCredential =
GoogleAccountCredential.usingOAuth2(context, scopes);
//mCredential.setSelectedAccount(new Account(personEmail, context.getString(R.string.account_type)));
mCredential.setSelectedAccountName(personEmail);
People service = new People.Builder(httpTransport, jsonFactory, mCredential)
.setApplicationName(context.getString(R.string.app_name)) // your app name
.build();
// Get info. on user
try {
userProfile = service.people().get("people/me").setRequestMaskIncludeField("person.biographies,person.birthdays,person.genders,person.phone_numbers").execute();
} catch (IOException e) {
FirebaseApp.initializeApp(context);
FirebaseCrash.report(e);
LogUtils.e(TAG, e.getMessage());
}
// Get whatever you want
if (userProfile != null) {
// Gender
List<Gender> genders = userProfile.getGenders();
if (genders != null && genders.size() > 0) {
Gender gender = genders.get(0);
if (gender != null) {
// save mGender
genderString = gender.getValue();
LogUtils.d(TAG, "mGender : " + gender.getValue());
}
}
// BirthDay
List<Birthday> birthdays = userProfile.getBirthdays();
if (birthdays != null && birthdays.size() > 0) {
Birthday birthday = birthdays.get(0);
if (birthday != null && birthday.getDate() != null && birthday.getDate().getYear() != null && birthday.getDate().getMonth() != null
&& birthday.getDate().getDay() != null) {
// save mBirthday
Calendar calendar = Calendar.getInstance();
calendar.set(birthday.getDate().getYear(), birthday.getDate().getMonth(), birthday.getDate().getDay());
birthdayLocal = calendar.getTime().getTime();
LogUtils.d(TAG, "mBirthday : " + birthday.toString());
}
}
// Phone Number
List<PhoneNumber> phoneNumbers = userProfile.getPhoneNumbers();
if (phoneNumbers != null && phoneNumbers.size() > 0) {
PhoneNumber phoneNumber = phoneNumbers.get(0);
if (phoneNumber != null) {
// save mPhoneNumber
phoneLocal = phoneNumber.getValue();
LogUtils.d(TAG, "mPhoneNumber : " + phoneNumber.getValue());
}
}
// biography (About me)
List<Biography> biographies = userProfile.getBiographies();
if (biographies != null && biographies.size() > 0) {
Biography biography = biographies.get(0);
if (biography != null) {
// save biography
aboutMeLocal = biography.getValue();
LogUtils.d(TAG, "biography : " + biography.getValue());
}
}
}
try{
ListConnectionsResponse response = service.people().connections()
.list("people/me")
// This line's really important! Here's why:
// http://stackoverflow.com/questions/35604406/retrieving-information-about-a-contact-with-google-people-api-java
.setRequestMaskIncludeField("person.names,person.emailAddresses,person.phoneNumbers,person.biographies")
.execute();
List<Person> connections = response.getConnections();
if(connections!=null && connections.size()>0){
for (Person person : connections) {
getPersonInfo(person);
}}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
iSignIn.onSaveUserInformation(personEmail, personFirstName, personLastName, image, genderString, birthdayLocal, phoneLocal, aboutMeLocal);
}
}
<Team Side="Home" TeamRef="ref123">
<Goal PlayerRef="p1111" Time="10" >
<Assist PlayerRef="p9999">p9999</Assist>
</Goal>
<Goal PlayerRef="p4444" Time="11" >
<Assist PlayerRef="p9999">p9999</Assist>
</Goal>
<Goal PlayerRef="p7777 Time="13" >
<Assist PlayerRef="p9999">p9999</Assist>
</Goal>
<Goal PlayerRef="p7777 Time="17" >
<Assist PlayerRef="p9999">p9999</Assist>
</Goal>
</Team>
public void GetScorer(string side, string OCompetition, string OSeason, string OGameId)
{
try
{
var xDoc = XDocument.Load(test);
var query = from q in xDoc.Descendants("Team")
where (string)q.Attribute("Side") == side
from d in q.Elements("Goal")
select new
{
TeamRef = q.Attribute("TeamRef").Value,
PlayerRef = d.Attribute("PlayerRef").Value,
Time = d.Attribute("Time").Value
};
var count = 0;
foreach (var qq in query)
{
if (side == "Home")
{
if (HomeSlateScorerList[count].PlayerRef != qq.PlayerRef)
{
HomeSlateScorerList.Add(new Scorer() { PlayerRef = qq.PlayerRef, Time = qq.Time, LastName = GetPlayerNameSlate(qq.PlayerRef, OSeason, OCompetition, OGameId) });
}
else
{
HomeSlateScorerList[count].Time = HomeSlateScorerList[count].Time + "' ";
}
}
if (side == "Away")
{
AwaySlateScorerList.Add(new Scorer() { PlayerRef = qq.PlayerRef, Time = qq.Time, LastName = GetPlayerNameSlate(qq.PlayerRef, OCompetition, OSeason, OGameId) });
}
count++;
}
}
catch (Exception)
{
// ignored
}
}
I would like to edit a player in a list of players
HomeSlateScorerList = new List<Scorer>();
AwaySlateScorerList = new List<Scorer>();
what I would like to achieve is for e.g. there are two players with the ref of "p7777" so in the list of object I would like to have one player with the playerref of "p7777" so if the player exist the format will be
playerref = "p7777"
Time = 13' 17'
or if one player its
Time = 13'
or if another goal is added to the xml its
Time = 13' 17' 25'
HomeSlateScorerList = HomeSlateScorerList
.GroupBy(s => s.PlayerRef)
.Select(g => new Scorer { PlayerRef = g.Key, Time = string.Join(", ", g.Select(v => v.Time)) })
.ToList();
Thanks to: #SergeyS SergeyS
I am trying to create a create combobox with performcallback, but I got the error. here this error "the model item passed into dictionary is of type 'system.Collections.Generic.Lost'1[System.String]', but this dix=ctionary requires a model item of type 'DIS_iDealer.Models.SalesMonitoringModel'"
I don't know which code exactly I need to paste but this is what I have:
view combobox :
#model DIS_iDealer.Models.SalesMonitoringModel
#Html.DevExpress().ComboBoxFor(m => m.mpmGroupLine.DESCRIPTION, settings =>
{
settings.Name = "Desc_ID_CB";
settings.Properties.IncrementalFilteringMode = IncrementalFilteringMode.Contains;
settings.Properties.DropDownStyle = DropDownStyle.DropDownList;
settings.CallbackRouteValues = new { Controller = "Report", Action = "cbPartialCategoryDetail" };
settings.Properties.CallbackPageSize = 50;
settings.Properties.ValueField = "DESCRIPTION";
settings.Properties.TextField = "DESCRIPTION";
settings.Width = 150;
settings.SelectedIndex = 0;
settings.Properties.ClientSideEvents.BeginCallback = "function(s,e){e.customArgs['group_Id'] = Category_Id_CB.GetValue()}";
settings.Properties.ValidationSettings.ErrorTextPosition = ErrorTextPosition.Right;
settings.Properties.ValidationSettings.ErrorDisplayMode = ErrorDisplayMode.ImageWithText;
settings.Properties.ValidationSettings.Display = Display.Dynamic;
}).BindList((List<string>)new DIS_iDealer.DataAccess.SalesMonitoringDAC().GetProductGroupDetail(Model.mpmGroupLine.GROUPID).ToList()).GetHtml()
controller :
namespace DIS_iDealer.Controllers
{
public class ReportController : BaseController
{
[HttpGet]
public ActionResult Report_SalesMonitoring_2()
{
SalesMonitoringModel mode = new SalesMonitoringModel();
//MPMPRODUCTGROUPLINE itemB = new MPMPRODUCTGROUPLINE();
/*List<string> mpmCate = mode.GetProductGroup();
if (mpmCate != null)
{
itemB.DESCRIPTION = mode.mpmGroupLine.DESCRIPTION;
}*/
ReportModels modelReport = new ReportModels();
if (TempData["ReportSalesMonitoring2"] != null)
{
modelReport = (ReportModels)TempData["ReportSalesMonitoring2"];
string reportParam = string.Empty;
foreach (string item in modelReport.ParameterReport)
{
reportParam += item;
}
ViewBag.IframeURL = modelReport.WebURL + reportParam;
}
return View(mode);
}
[ValidateInput(false)]
public ActionResult cbPartialCategoryDetail(string group_Id)
{
//SalesMonitoringModel model = new SalesMonitoringModel();
SalesMonitoringDAC model = new SalesMonitoringDAC();
List<string> itemDetail = model.GetProductGroupDetail(group_Id);
return PartialView("_cbPartialCategoryDetail", itemDetail);
}
}
}
Please let me know if you need more info. Thanks.
I have to unit test one of the very big and unformatted method in ASP.NET MVC 4.0.
Below is the code of action method :-
public ActionResult GetDetails(string ProdName, int ProdArea, int ProdAreaId= 0)
{
if (ProdAreaId == 0 && ProdArea == 1 && System.Web.HttpContext.Current.Session["ResponseProdAreaId"] != null)
{
ProdAreaId = (int)System.Web.HttpContext.Current.Session["ResponseProdAreaId"];
}
if (string.IsNullOrEmpty(ProdName))
{
if (System.Web.HttpContext.Current.Session["ProdName"] == null)
{
ProdName = Guid.NewGuid().ToString();
System.Web.HttpContext.Current.Session["ProdName"] = ProdName;
}
else
{
ProdName = System.Web.HttpContext.Current.Session["ProdName"].ToString();
}
}
else
{
ProdName = ProdName.Replace("___", " ");
}
List<StateDetail> stateList = ProductService.GetAllStates().Where(n => n.FKCountryID == (int)Countries.UnitedStates).ToList();
ProductAddressViewModel model = new ProductAddressViewModel
{
ProdArea = ProdArea,
FKProductID = CurrentProductId
};
model.States = stateList != null ? new SelectList(stateList, "StateID", "StateCode") : null;
if (System.Web.HttpContext.Current.Session[“ProdAddresses”] != null && ProdAreaId == 0 && ProdArea == 1)
{
List<ProductAddressDto> lstprodaddresses = (List<ProductAddressDto>)System.Web.HttpContext.Current.Session[“ProdAddresses”];
if (lstprodaddresses.Count > 0)
{
AddressDto addrDto = lstprodaddresses.First().Address;
//save address in DB
model.Address1 = addrDto.Address1;
model.Address2 = addrDto.Address2;
model.ProdArea = 1;
model.City = addrDto.City;
model.IsDefault = true;
model.ProdName = model.ProdName;
model.SelectedAddressTypeID = (int)AddressType.Street;
model.ZIPCode = addrDto.ZIPCode;
model.SelectedStateId = addrDto.FKStateID;
model.AddressTypes = GetAddressTypes();
}
}
else if (model.FKProductID > 0)
{
ToolDto tool = ToolService.GetToolDetails(model.FKProductID);
if (ProdAreaId > 0)
{
model.AddressTypes = GetAddressTypes();
ProductAddressDto prodaddr = tool.ToolAddresses.First(n => n.Tool_AddressID == ProdAreaId);
model.Address1 = prodaddr.Address.Address1;
model.Address2 = prodaddr.Address.Address2;
model.City = prodaddr.Address.City;
model.SelectedStateId = prodaddr.Address.FKStateID;
model.ZIPCode = prodaddr.Address.ZIPCode;
model.SelectedAddressTypeID = prodaddr.Address.FKAddressTypeID;
model.IsDefault = prodaddr.IsDefault;
model.FKAddressID = prodaddr.FKAddressID;
model.Tool_AddressID = prodaddr.Tool_AddressID;
model.FKProductID = prodaddr.FKProductID;
model.AddressTypes = GetAddressTypes();
}
else
{
//address types
List<int> excludeAddrTypes = new List<int>();
foreach (ProductAddressDto prodadrdto in tool.ToolAddresses)
{
if (prodadrdto.Tool_AddressID != ProdAreaId)
{
excludeAddrTypes.Add(prodadrdto.Address.FKAddressTypeID);
}
}
if (System.Web.HttpContext.Current.Session[“ProdAddresses”] != null)
{
excludeAddrTypes.Add((int)AddressType.Street);
}
var addrtypes = from AddressType e in Enum.GetValues(typeof(AddressType))
where !excludeAddrTypes.Contains((int)e)
select new { Id = (int)e, Name = e.ToString() };
model.AddressTypes = addrtypes.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
if (tool.ToolAddresses.Count == 0)
{
model.IsDefault = (ProdArea == 1);
}
}
}
else
{
//filter out address types if responsed tool is there
if (System.Web.HttpContext.Current.Session[“ProdAddresses”] != null)
{
List<int> excludeAddrTypes = new List<int>();
excludeAddrTypes.Add((int)AddressType.Street);
var addrtypes = from AddressType e in Enum.GetValues(typeof(AddressType))
where !excludeAddrTypes.Contains((int)e)
select new { Id = (int)e, Name = e.ToString() };
model.AddressTypes = addrtypes.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
}
else
{
model.AddressTypes = GetAddressTypes();
}
model.IsDefault = (ProdArea == 1);
}
model.ProdName = ProdName;
return PartialView("_AddUpdateAddress", model);
}
May be the method is not in correct format.But i have to do it's unit testing.I have do that in several different ways.But i am not sure about its correctness.
I want to know that how should we do unit test for such a big and unformatted method like this.
Can anyone help me out on this ?
Below is the piece of code of my unit testing method :-
[TestMethod]
public void GetDetailsTest_NotEmpty()
{
var ProdName = random.ToString();
var ProdArea = random.Next();
var ProdAreaId = 0;
var _toolServiceMock = new Mock<IToolService>();
var _lookupServiceMock = new Mock<ILookupService>();
var stateList = new List<StateDto> {
new StateDto() { StateID = random.Next(), StateCode = Guid.NewGuid().ToString(), Description = random.ToString(), FKCountryID = 1 },
new StateDto() { StateID = random.Next(), StateCode = Guid.NewGuid().ToString(), Description = random.ToString(), FKCountryID = random.Next() },
};
_lookupServiceMock.Setup(s => s.GetAllStates()).Returns(stateList); // .Returns(stateList);
//Arrange
CustomerDto cust = _toolService.LookupCustomers("", "").FirstOrDefault();
if (cust != null)
{
ToolDto tool = _toolService.GetToolDetails(cust.Tool.toolId);
if (tool.ToolAddresses.Count > 0 && tool.ToolAddresses.First().Address != null)
{
HttpContext.Current.Session["FKToolID"] = cust.FKToolID;
var controller = new ToolController(_toolServiceMock.Object);
PartialViewResult result = controller.SelectAddress(cust.Tool.Name, 1, tool.ToolAddresses.First().Tool_AddressID) as PartialViewResult;
var viewmodel = (ToolAddressViewModel)((ViewResultBase)(result)).Model;
if (viewmodel != null)
{
//Act
Assert.AreEqual(tool.ToolAddresses.First().Address.Address1, viewmodel.Address1);
Assert.AreEqual("_AddUpdateAddress", result.ViewName);
Assert.IsInstanceOfType(viewmodel, typeof(ToolAddressViewModel));
}
//Act
_lookupServiceMock.VerifyAll();
}
}
}
First of all, your method is a way too complicated and too long.
Make your actions short and with one responsability to respect SOLID principle (http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)).
It will make your methods easier to test.
To help you with your problem, you can do something quick with your code :
Split your actions in internal virtual methods with one concern each (internal to be testable in you unit test project, virtual to be able to mock them).
Put the [assembly: InternalsVisibleTo("YourTestAssembly")] on your controller
In your unit test project, you will be able to test any of your internal methods separately.
Finally, to test your action, use a mocking framework (RhinoMock, Moq) to mock all your internal virtual methods and test the logic of your action (proxies generated by mocking framework will let you mock virtual methods).
It's the easiest way you can test your logic without breaking the existing application.
The inconvenient is that your logic is in internal methods wich let the whole assembly able to use it.